React Examples
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)
})
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.
- cypress/support/component.jsx
- Typings
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)
})
import { MountOptions, MountReturn } from 'cypress/react'
import { MemoryRouterProps } from 'react-router-dom'
declare global {
namespace Cypress {
interface Chainable {
/**
* Mounts a React node
* @param component React Node to mount
* @param options Additional options to pass into mount
*/
mount(
component: React.ReactNode,
options?: MountOptions & { routerProps?: MemoryRouterProps }
): Cypress.Chainable<MountReturn>
}
}
}
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:
- cypress/support/component.jsx
- Typings
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)
})
import { MountOptions, MountReturn } from 'cypress/react'
import { EnhancedStore } from '@reduxjs/toolkit'
import { RootState } from './src/StoreState'
declare global {
namespace Cypress {
interface Chainable {
/**
* Mounts a React node
* @param component React Node to mount
* @param options Additional options to pass into mount
*/
mount(
component: React.ReactNode,
options?: MountOptions & { reduxStore?: EnhancedStore<RootState> }
): Cypress.Chainable<MountReturn>
}
}
}
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)
})
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.