The most surprising thing about testing React components in isolation with Cypress is that you’re not just testing JavaScript; you’re testing the DOM.

Let’s see this in action. Imagine you have a simple Button component:

// src/components/Button.jsx
import React from 'react';

function Button({ onClick, children, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
}

export default Button;

Here’s how you’d mount and test it using Cypress Component Testing:

// src/components/Button.cy.jsx
import React from 'react';
import Button from './Button';

describe('<Button />', () => {
  it('renders with correct text and is clickable', () => {
    const handleClick = cy.stub().as('clickStub');
    cy.mount(<Button onClick={handleClick}>Click Me</Button>);
    cy.get('button').should('contain.text', 'Click Me');
    cy.get('button').click();
    cy.get('@clickStub').should('have.been.calledOnce');
  });

  it('is disabled when the disabled prop is true', () => {
    cy.mount(<Button disabled>Disabled Button</Button>);
    cy.get('button').should('be.disabled');
  });
});

When you run npm run cypress:open, you’ll see a browser window pop up with your Button component rendered. Cypress interacts with this rendered DOM directly, allowing you to assert its state and behavior as if a user were interacting with it. You can see the button, click it, and verify that the onClick handler fired. You can also test its disabled state.

The problem this solves is the brittle nature of traditional unit tests for UI components. Mocking out every single DOM interaction, event, and CSS style can become incredibly complex and lead to tests that break when the UI looks different but functions the same. Component testing, by interacting with the actual DOM produced by your React code, grounds your tests in observable behavior.

Internally, Cypress Component Testing works by spinning up a development server (like Webpack Dev Server or Vite) that bundles your component and its dependencies. It then mounts your component into a real browser DOM within an iframe. Cypress’s command API then interacts with this DOM. This means cy.get('button') isn’t looking at a virtual DOM representation; it’s looking at the actual <button> element in the page. You control the props passed to your component during cy.mount(), simulating different states and inputs.

This approach allows for a rich set of assertions. You can check for attributes, styles, ARIA roles, and even use cy.percySnapshot() for visual regression testing. The mental model is that you are a user interacting with a single component, not a developer debugging a React tree. You’re concerned with what the user sees and can do.

The cy.mount() command is a custom command provided by the Cypress component testing adapter for React. It takes your React element and renders it into the test runner’s DOM. The adapter handles the necessary setup, like creating a React root and rendering your component. The key is that this happens within the Cypress test runner’s browser environment, giving you access to all of Cypress’s powerful commands for interacting with the DOM.

When you think about prop drilling, it’s easy to get lost in the complexity of passing state down multiple levels. However, with component testing, you can directly mount a component with the exact props it would receive at that level, effectively bypassing the need to test the entire prop-drilling mechanism itself. For instance, if your Button component receives a variant prop to change its styling, you can test it like this:

// src/components/Button.cy.jsx (continued)
it('applies primary styling when variant is "primary"', () => {
  cy.mount(<Button onClick={() => {}} variant="primary">Primary</Button>);
  cy.get('button').should('have.class', 'button-primary'); // Assuming a CSS class is applied
});

This focuses your test on the Button’s responsibility: how it renders and behaves given a specific variant prop, without needing to test the parent component that would pass that prop.

The next concept you’ll likely explore is how to handle and test asynchronous behavior within your components, such as data fetching or animations.

Want structured learning?

Take the full Cypress course →