Mount and test React components in isolation with full Cypress testing capabilities. The @cypress/react package provides seamless integration between React components and Cypress's powerful testing commands.
Mount React components for isolated testing with props, context, and full Cypress command support.
// From cypress/react
import { mount } from '@cypress/react';
/**
* Mount a React component for testing
* @param jsx - React JSX element to mount
* @param options - Mounting options and configuration
* @returns Cypress chainable with component and rerender function
*/
function mount(
jsx: React.JSX.Element,
options?: MountOptions
): Cypress.Chainable<MountReturn>;
interface MountOptions {
/** Log the mounting command into Cypress Command Log, true by default */
log?: boolean;
/** Render component in React strict mode */
strict?: boolean;
/** ReactDOM instance to use for rendering */
ReactDom?: typeof import('react-dom/client');
}
interface MountReturn {
/** The component that was rendered */
component: React.ReactNode;
/** Rerenders the specified component with new props */
rerender: (component: React.ReactNode) => Cypress.Chainable<MountReturn>;
}Usage Examples:
// cypress/component/Button.cy.js
import { mount } from '@cypress/react';
import Button from './Button';
describe('Button Component', () => {
it('renders with text', () => {
mount(<Button>Click me</Button>);
cy.contains('Click me').should('be.visible');
});
it('handles click events', () => {
const handleClick = cy.stub();
mount(<Button onClick={handleClick}>Click me</Button>);
cy.contains('Click me').click();
cy.wrap(handleClick).should('have.been.called');
});
it('supports different variants', () => {
mount(<Button variant="primary">Primary Button</Button>);
cy.get('button').should('have.class', 'btn-primary');
});
});Pass props to components and test different configurations.
// Testing with various props
describe('UserCard Component', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.jpg'
};
it('displays user information', () => {
mount(<UserCard user={mockUser} />);
cy.contains('John Doe').should('be.visible');
cy.contains('john@example.com').should('be.visible');
cy.get('[data-cy=avatar]').should('have.attr', 'src', mockUser.avatar);
});
it('handles missing avatar', () => {
const userWithoutAvatar = { ...mockUser, avatar: null };
mount(<UserCard user={userWithoutAvatar} />);
cy.get('[data-cy=avatar-placeholder]').should('be.visible');
});
it('updates when props change via rerender', () => {
mount(<UserCard user={mockUser} />).then(({ rerender }) => {
cy.contains('John Doe').should('be.visible');
// Update user data
const updatedUser = { ...mockUser, name: 'Jane Doe' };
rerender(<UserCard user={updatedUser} />);
cy.contains('Jane Doe').should('be.visible');
cy.contains('John Doe').should('not.exist');
});
});
});Test components that depend on React Context.
// Testing with Context providers
import { ThemeProvider } from '@mui/material/styles';
import { mount } from '@cypress/react';
import ThemedButton from './ThemedButton';
describe('ThemedButton Component', () => {
const lightTheme = createTheme({ palette: { mode: 'light' } });
const darkTheme = createTheme({ palette: { mode: 'dark' } });
it('renders with light theme', () => {
mount(
<ThemeProvider theme={lightTheme}>
<ThemedButton>Light Button</ThemedButton>
</ThemeProvider>
);
cy.get('button').should('have.class', 'light-theme');
});
it('renders with dark theme', () => {
mount(
<ThemeProvider theme={darkTheme}>
<ThemedButton>Dark Button</ThemedButton>
</ThemeProvider>
);
cy.get('button').should('have.class', 'dark-theme');
});
});Test components that use React Router for navigation.
import { mount } from '@cypress/react';
import { MemoryRouter } from 'react-router-dom';
import Navigation from './Navigation';
describe('Navigation Component', () => {
it('highlights active route', () => {
mount(
<MemoryRouter initialEntries={['/dashboard']}>
<Navigation />
</MemoryRouter>
);
cy.get('[data-cy=nav-dashboard]').should('have.class', 'active');
cy.get('[data-cy=nav-profile]').should('not.have.class', 'active');
});
it('navigates between routes', () => {
mount(
<MemoryRouter initialEntries={['/']}>
<Navigation />
</MemoryRouter>
);
cy.get('[data-cy=nav-profile]').click();
cy.url().should('include', '/profile');
});
});Test complex user interactions with forms and input handling.
import { mount } from '@cypress/react';
import LoginForm from './LoginForm';
describe('LoginForm Component', () => {
it('validates required fields', () => {
const onSubmit = cy.stub();
mount(<LoginForm onSubmit={onSubmit} />);
cy.get('button[type=submit]').click();
cy.contains('Email is required').should('be.visible');
cy.contains('Password is required').should('be.visible');
cy.wrap(onSubmit).should('not.have.been.called');
});
it('submits valid form data', () => {
const onSubmit = cy.stub();
mount(<LoginForm onSubmit={onSubmit} />);
cy.get('[data-cy=email-input]').type('user@example.com');
cy.get('[data-cy=password-input]').type('password123');
cy.get('button[type=submit]').click();
cy.wrap(onSubmit).should('have.been.calledWith', {
email: 'user@example.com',
password: 'password123'
});
});
it('displays loading state during submission', () => {
mount(<LoginForm onSubmit={() => new Promise(() => {})} />);
cy.get('[data-cy=email-input]').type('user@example.com');
cy.get('[data-cy=password-input]').type('password123');
cy.get('button[type=submit]').click();
cy.get('button[type=submit]').should('be.disabled');
cy.contains('Logging in...').should('be.visible');
});
});Test components connected to Redux store.
import { mount } from '@cypress/react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import TodoList from './TodoList';
import todoReducer from './todoReducer';
describe('TodoList Component', () => {
const createMockStore = (initialState) => {
return createStore(todoReducer, initialState);
};
it('displays todos from store', () => {
const store = createMockStore({
todos: [
{ id: 1, text: 'Learn Cypress', completed: false },
{ id: 2, text: 'Write tests', completed: true }
]
});
mount(
<Provider store={store}>
<TodoList />
</Provider>
);
cy.contains('Learn Cypress').should('be.visible');
cy.contains('Write tests').should('be.visible');
cy.get('[data-cy=todo-item]').should('have.length', 2);
});
it('dispatches actions on user interaction', () => {
const store = createMockStore({ todos: [] });
const dispatchSpy = cy.spy(store, 'dispatch');
mount(
<Provider store={store}>
<TodoList />
</Provider>
);
cy.get('[data-cy=add-todo-input]').type('New todo');
cy.get('[data-cy=add-todo-button]').click();
cy.wrap(dispatchSpy).should('have.been.calledWith', {
type: 'ADD_TODO',
payload: 'New todo'
});
});
});// Testing custom hooks with components
import { mount } from '@cypress/react';
import { useCounter } from './useCounter';
function CounterTestComponent() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<span data-cy="count">{count}</span>
<button data-cy="increment" onClick={increment}>+</button>
<button data-cy="decrement" onClick={decrement}>-</button>
<button data-cy="reset" onClick={reset}>Reset</button>
</div>
);
}
describe('useCounter Hook', () => {
it('manages counter state', () => {
mount(<CounterTestComponent />);
cy.get('[data-cy=count]').should('contain', '0');
cy.get('[data-cy=increment]').click();
cy.get('[data-cy=count]').should('contain', '1');
cy.get('[data-cy=decrement]').click();
cy.get('[data-cy=count]').should('contain', '0');
cy.get('[data-cy=increment]').click().click().click();
cy.get('[data-cy=count]').should('contain', '3');
cy.get('[data-cy=reset]').click();
cy.get('[data-cy=count]').should('contain', '0');
});
});Configure component testing in your Cypress configuration:
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
component: {
devServer: {
framework: 'create-react-app',
bundler: 'webpack',
},
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/component.js',
indexHtmlFile: 'cypress/support/component-index.html'
}
});Access mounting utilities for custom setup and teardown:
// From cypress/mount-utils
const ROOT_SELECTOR = '[data-cy-root]';
function getContainerEl(): HTMLElement;
function setupHooks(optionalCallback?: Function): void;Custom Component Setup:
// cypress/support/component.js
import { mount } from '@cypress/react';
import { getContainerEl } from '@cypress/mount-utils';
// Custom mount command
Cypress.Commands.add('mountWithProviders', (component, options = {}) => {
const { providers = [], ...mountOptions } = options;
let wrappedComponent = component;
providers.forEach(Provider => {
wrappedComponent = <Provider>{wrappedComponent}</Provider>;
});
return mount(wrappedComponent, mountOptions);
});
// Global component setup
beforeEach(() => {
// Reset any global state
// Clear local storage, etc.
});The React component testing integration provides a powerful way to test React components in isolation while leveraging all of Cypress's testing capabilities, including real browser rendering, network stubbing, and comprehensive assertion libraries.