Skip to main content
Cypress App

React Examples

info

What you'll learn

  • How to mount a React component
  • How to pass data to a React component
  • How to test event handlers
  • How to customize cy.mount() for React Router and Redux

Mounting Components​

Mounting a Component​

The first step in testing a component is to mount it. This renders the component into a testbed and enable's the use of the Cypress API to select elements, interact with them, and run assertions.

To mount a React component, import the component into your spec and pass the component to the cy.mount command:

import { Stepper } from './stepper'

it('mounts', () => {
cy.mount(<Stepper />)
//Stepper should have initial count of 0 (default)
cy.get('[data-cy=counter]').should('have.text', '0')
})

Passing Data to a Component​

You can pass props to a component by setting them on the JSX passed into cy.mount():

it('mounts', () => {
cy.mount(<Stepper initial={100} />)
//Stepper should have initial count of 100
cy.get('[data-cy=counter]').should('have.text', '100')
})

Testing Event Handlers​

Pass a Cypress spy to an event prop and validate it was called:

it('clicking + fires a change event with the incremented value', () => {
const onChangeSpy = cy.spy().as('onChangeSpy')
cy.mount(<Stepper onChange={onChangeSpy} />)
cy.get('[data-cy=increment]').click()
cy.get('@onChangeSpy').should('have.been.calledWith', 1)
})

Testing Error States​

cy.mount() is an asynchronous Cypress command: it enqueues the mount and returns immediately, before the component ever renders. When a component throws during render, the error surfaces as an uncaught exception rather than as a synchronous throw.

By default Cypress fails the test on any uncaught exception, so to assert on a render error you listen for it with cy.on('uncaught:exception') and return false to prevent Cypress from failing the test:

import { UserProfile } from './UserProfile'

it('surfaces a render error', () => {
cy.on('uncaught:exception', (err) => {
// Assert on the error thrown during render...
expect(err.message).to.include('user is required')

// ...and return false so Cypress does not fail the test.
return false
})

cy.mount(<UserProfile />)
})

Testing Error Boundaries​

The recommended way to handle render errors in React is an Error Boundary, which renders fallback UI via getDerivedStateFromError(). You can mount a component wrapped in your Error Boundary and assert that the fallback UI is displayed:

// ErrorBoundary.cy.jsx
import { ErrorBoundary } from './ErrorBoundary'

const errorMessage = 'I crashed!'
const ChildWithError = () => {
throw new Error(errorMessage)
}

it('displays the fallback UI on error', () => {
// An Error Boundary renders fallback UI, but it does NOT stop the error
// from propagating to Cypress as an uncaught exception. Cypress fails on
// uncaught exceptions by default, so we must still suppress that behavior.
cy.on('uncaught:exception', (err) => {
// Only suppress the specific error we expect.
expect(err.message).to.include(errorMessage)

return false
})

cy.mount(
<ErrorBoundary name="ChildWithError">
<ChildWithError />
</ErrorBoundary>
)

cy.get('header h1').should('contain', 'Something went wrong.')
})

Where ErrorBoundary renders fallback UI from getDerivedStateFromError():

// ErrorBoundary.jsx
import React from 'react'

export class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { error: null }
}

static getDerivedStateFromError(error) {
return { error }
}

render() {
const { name } = this.props
const { error } = this.state

if (error) {
return (
<header>
<h1>Something went wrong.</h1>
<h2>{`${name} failed to load`}</h2>
</header>
)
}

return this.props.children
}
}

Custom Mount Commands​

Customizing cy.mount()​

By default, cy.mount() is a simple passthrough to mount(), however, you can customize cy.mount() to fit your needs. For instance, if you are using providers or other global app-level setups in your React app, you can configure them here.

Below are a few examples that demonstrate using a custom mount command. These examples can be adjusted for most other providers that you will need to support.

React Router​

If you have a component that consumes a hook or component from React Router, make sure the component has access to a React Router provider. Below is a sample mount command that uses MemoryRouter to wrap the component.

import { mount } from 'cypress/react'
import { MemoryRouter } from 'react-router-dom'

Cypress.Commands.add('mount', (component, options = {}) => {
const { routerProps = { initialEntries: ['/'] }, ...mountOptions } = options

const wrapped = <MemoryRouter {...routerProps}>{component}</MemoryRouter>

return mount(wrapped, mountOptions)
})

To set up certain scenarios, pass in props that will get passed to MemoryRouter in the options. Below is an example test that ensures an active link has the correct class applied to it by initializing the router with initialEntries pointed to a particular route:

import { Navigation } from './Navigation'

it('home link should be active when url is "/"', () => {
// No need to pass in custom initialEntries as default url is '/'
cy.mount(<Navigation />)

cy.get('a').contains('Home').should('have.class', 'active')
})

it('login link should be active when url is "/login"', () => {
cy.mount(<Navigation />, {
routerProps: {
initialEntries: ['/login'],
},
})

cy.get('a').contains('Login').should('have.class', 'active')
})

Redux​

To use a component that consumes state or actions from a Redux store, create a mount command that will wrap your component in a Redux Provider:

import { mount } from 'cypress/react'
import { Provider } from 'react-redux'
import { getStore } from '../../src/store'

Cypress.Commands.add('mount', (component, options = {}) => {
// Use the default store if one is not provided
const { reduxStore = getStore(), ...mountOptions } = options

const wrapped = <Provider store={reduxStore}>{component}</Provider>

return mount(wrapped, mountOptions)
})

The options param can have a store that is already initialized with data:

import { getStore } from '../redux/store'
import { setUser } from '../redux/userSlice'
import { UserProfile } from './UserProfile'

it('User profile should display user name', () => {
const user = { name: 'test person' }

// getStore is a factory method that creates a new store
const store = getStore()

// setUser is an action exported from the user slice
store.dispatch(setUser(user))

cy.mount(<UserProfile />, { reduxStore: store })

cy.get('div.name').should('have.text', user.name)
})
info

The getStore method is a factory method that initializes a new Redux store. It is important that the store be initialized with each new test to ensure changes to the store don't affect other tests.