Q22 of 48 · Cypress
What is component testing in Cypress and when would you use it over E2E?
Short answer
Short answer: Cypress component testing mounts a single component in isolation and tests it like a real browser — clicks, hovers, accessibility — without rendering the full app. Use it for UI components with rich behaviour (forms, complex state) where a unit test wouldn't catch DOM bugs but full E2E is overkill.
Detail
Cypress component testing (introduced in 10.0) mounts a single React/Vue/Angular component into a real browser and runs your tests against it. It sits between unit tests (no DOM) and E2E tests (full app):
- Unit test (Jest + JSDOM): no real browser, no real CSS, can't test layout or interactivity faithfully.
- Component test (Cypress CT): real browser, real CSS, real interactivity, but only one component — fast and isolated.
- E2E (Cypress): full app, all the network and routing, but slow.
When component testing is the right fit:
- Complex interactive components (date picker, autocomplete, drag-and-drop).
- Behaviour that depends on real CSS (sticky positioning, focus styles, transitions).
- Form validation paths that don't need backend.
- Visual regression of a single component.
- Component-level accessibility (axe-core works in CT).
When E2E is still required:
- Routing, navigation, auth flows.
- Anything that depends on the real backend or real fixtures.
- User journeys spanning multiple pages.
The configuration adds a component block to cypress.config.ts with a dev server (Vite, Webpack, Next). Tests live in cypress/component/ (or co-located) and are typically named *.cy.tsx. The cy.mount(<Component />) command (provided by the framework integration) puts the component in the test runner.
A common mistake is using component tests for everything UI-shaped. They're not a replacement for E2E — the routing, real API, and journey verification still belong there.
// EXAMPLE
Counter.cy.tsx
import { Counter } from './Counter';
describe('Counter', () => {
it('increments and decrements', () => {
cy.mount(<Counter initial={3} />);
cy.get('[data-test=value]').should('have.text', '3');
cy.get('[data-test=inc]').click();
cy.get('[data-test=value]').should('have.text', '4');
cy.get('[data-test=dec]').click().click();
cy.get('[data-test=value]').should('have.text', '2');
});
it('clamps at zero', () => {
cy.mount(<Counter initial={0} />);
cy.get('[data-test=dec]').click();
cy.get('[data-test=value]').should('have.text', '0');
cy.get('[data-test=dec]').should('be.disabled');
});
});