Cypress Component Testing: Test React, Vue, and Angular Components
Cypress Component Testing lets you mount individual components in a real browser and test them in isolation — without starting your full application. This approach fills the gap between unit tests (jsdom, fast but imprecise) and E2E tests (full browser, accurate but slow and expensive to maintain).
This guide covers setup for React, Vue, and Angular, along with mounting, props, events, mocking, and visual testing.
What Is Component Testing?
In component testing, Cypress:
- Starts a development server (via your existing Vite, webpack, or Angular CLI config)
- Mounts your component in a blank page in the browser
- Runs your test against the mounted component
You get:
- Real browser rendering, including CSS, animations, and media queries
- Cypress's full locator and assertion API
- Network interception via
cy.intercept() - Screenshots and video on failure
- Time travel debugging
Unlike jsdom-based unit tests, CSS actually works and browser APIs behave exactly as they do in production.
Setup: React
Install the Cypress component testing dependencies:
npm install --save-dev cypress @cypress/reactOpen Cypress:
npx cypress openIn the Launchpad, choose Component Testing and select your framework (React) and bundler (Vite or webpack). Cypress auto-detects your config and generates the necessary setup files.
The generated cypress.config.ts:
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
});Your First React Component Test
// src/components/Button/Button.cy.tsx
import { Button } from './Button';
describe('Button', () => {
it('renders the label', () => {
cy.mount(<Button label="Save" onClick={() => {}} />);
cy.get('button').should('contain.text', 'Save');
});
it('calls onClick when clicked', () => {
const onClickSpy = cy.spy().as('onClickSpy');
cy.mount(<Button label="Submit" onClick={onClickSpy} />);
cy.get('button').click();
cy.get('@onClickSpy').should('have.been.calledOnce');
});
it('is disabled when disabled prop is true', () => {
cy.mount(<Button label="Delete" onClick={() => {}} disabled />);
cy.get('button').should('be.disabled');
});
});Run component tests:
npx cypress run --componentOr open the interactive runner:
npx cypress open --componentTesting Forms in React
// src/components/ContactForm/ContactForm.cy.tsx
import { ContactForm } from './ContactForm';
describe('ContactForm', () => {
it('submits name and email', () => {
const onSubmitSpy = cy.spy().as('submit');
cy.mount(<ContactForm onSubmit={onSubmitSpy} />);
cy.get('[data-cy="name"]').type('Alice Smith');
cy.get('[data-cy="email"]').type('alice@example.com');
cy.get('[data-cy="message"]').type('Hello there');
cy.get('[data-cy="send"]').click();
cy.get('@submit').should('have.been.calledWith', {
name: 'Alice Smith',
email: 'alice@example.com',
message: 'Hello there',
});
});
it('shows validation errors for empty fields', () => {
cy.mount(<ContactForm onSubmit={() => {}} />);
cy.get('[data-cy="send"]').click();
cy.get('[data-cy="name-error"]').should('contain', 'Name is required');
cy.get('[data-cy="email-error"]').should('contain', 'Email is required');
});
});Wrapping with Context Providers
Real components often depend on React context (auth, theme, router). Wrap them with the same providers used in your app:
// cypress/support/component.tsx
import { mount } from 'cypress/react';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from '../../src/context/AuthContext';
import { ThemeProvider } from '../../src/context/ThemeContext';
Cypress.Commands.add('mount', (component, options = {}) => {
const Wrapper = ({ children }) => (
<BrowserRouter>
<AuthProvider>
<ThemeProvider>
{children}
</ThemeProvider>
</AuthProvider>
</BrowserRouter>
);
return mount(<Wrapper>{component}</Wrapper>, options);
});Now every cy.mount() call in your tests automatically includes all providers.
Mocking API Calls
Components that fetch data need their network calls intercepted:
describe('UserList', () => {
it('renders users from API', () => {
cy.intercept('GET', '/api/users', {
fixture: 'users.json',
}).as('getUsers');
cy.mount(<UserList />);
cy.wait('@getUsers');
cy.get('[data-cy="user-item"]').should('have.length', 3);
cy.contains('Alice Smith').should('be.visible');
});
it('shows empty state when no users', () => {
cy.intercept('GET', '/api/users', { body: [] });
cy.mount(<UserList />);
cy.get('[data-cy="empty-state"]').should('be.visible');
});
it('shows error message when API fails', () => {
cy.intercept('GET', '/api/users', { statusCode: 500 });
cy.mount(<UserList />);
cy.get('[data-cy="error-message"]').should('contain', 'Failed to load');
});
});Create cypress/fixtures/users.json:
[
{ "id": 1, "name": "Alice Smith", "email": "alice@example.com" },
{ "id": 2, "name": "Bob Jones", "email": "bob@example.com" },
{ "id": 3, "name": "Carol White", "email": "carol@example.com" }
]Setup: Vue
For Vue 3 with Vite:
npm install --save-dev cypressRun npx cypress open, choose Component Testing, and select Vue + Vite. The auto-generated config:
export default defineConfig({
component: {
devServer: {
framework: 'vue',
bundler: 'vite',
},
},
});Testing a Vue Component
// src/components/Counter/Counter.cy.ts
import Counter from './Counter.vue';
describe('Counter', () => {
it('starts at zero', () => {
cy.mount(Counter);
cy.get('[data-cy="count"]').should('have.text', '0');
});
it('increments on button click', () => {
cy.mount(Counter);
cy.get('[data-cy="increment"]').click();
cy.get('[data-cy="count"]').should('have.text', '1');
});
it('accepts an initial count via prop', () => {
cy.mount(Counter, { props: { initialCount: 10 } });
cy.get('[data-cy="count"]').should('have.text', '10');
});
it('emits update event when incremented', () => {
const onUpdateSpy = cy.spy().as('onUpdate');
cy.mount(Counter, {
props: { 'onUpdate': onUpdateSpy },
});
cy.get('[data-cy="increment"]').click();
cy.get('@onUpdate').should('have.been.calledWith', 1);
});
});Setup: Angular
For Angular:
npm install --save-dev cypress @cypress/schematic
ng add @cypress/schematicSelect Component Testing during setup. The schematic updates angular.json automatically.
Testing an Angular Component
// src/app/alert/alert.component.cy.ts
import { AlertComponent } from './alert.component';
describe('AlertComponent', () => {
it('renders with the correct message', () => {
cy.mount(AlertComponent, {
componentProperties: {
message: 'Something went wrong',
type: 'error',
},
});
cy.get('[data-cy="alert"]').should('contain.text', 'Something went wrong');
cy.get('[data-cy="alert"]').should('have.class', 'alert--error');
});
it('emits dismiss event when closed', () => {
const dismissSpy = cy.spy().as('dismiss');
cy.mount(AlertComponent, {
componentProperties: {
message: 'Info',
type: 'info',
dismiss: dismissSpy,
},
});
cy.get('[data-cy="close"]').click();
cy.get('@dismiss').should('have.been.calledOnce');
});
});Visual Snapshot Testing
Cypress supports screenshot-based visual regression testing through plugins:
it('matches visual snapshot', () => {
cy.mount(<Button label="Primary" variant="primary" />);
cy.get('button').matchImageSnapshot('button-primary');
});On first run, a baseline screenshot is saved. Subsequent runs diff against the baseline and fail if pixels exceed the configured threshold.
Component Tests vs E2E Tests
Use component tests for:
- Unit-level component behavior (props, events, state)
- Visual appearance across states (loading, error, empty, full)
- Edge cases that are hard to reproduce in a full app
Use E2E tests for:
- Multi-page user flows
- Real authentication
- Integration with a real backend
- Third-party services (payments, OAuth)
Component tests are dramatically faster — most run in under a second. A well-structured test suite has many component tests and fewer, higher-value E2E tests.
Organizing Component Tests
src/
components/
Button/
Button.tsx
Button.cy.tsx ← component test
UserList/
UserList.tsx
UserList.cy.tsx
pages/
Dashboard/
Dashboard.tsx
Dashboard.cy.tsx
cypress/
e2e/
auth.cy.ts ← E2E flows
support/
component.tsx ← mount wrapper with providers
commands.ts ← custom commands
fixtures/
users.jsonCI Configuration
Run component tests in your pipeline:
# .github/workflows/test.yml
- name: Run component tests
run: npx cypress run --component --browser chrome
- name: Run E2E tests
run: npx cypress run --browser chromeComponent tests don't require a running server, so they're faster and cheaper to run on every PR.
Summary
Cypress Component Testing gives you accurate, real-browser component tests with the same API you use for E2E tests:
- Works with React, Vue, and Angular
- Wrap components with real context providers
- Mock APIs with
cy.intercept()and fixtures - Debug with time travel and screenshots
- Run in CI without a full application server
It's the fastest way to catch component-level regressions before they reach production.