Cypress Component Testing: Test React, Vue, and Angular Components

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:

  1. Starts a development server (via your existing Vite, webpack, or Angular CLI config)
  2. Mounts your component in a blank page in the browser
  3. 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/react

Open Cypress:

npx cypress open

In 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 --component

Or open the interactive runner:

npx cypress open --component

Testing 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 cypress

Run 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/schematic

Select 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.json

CI 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 chrome

Component 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.

Read more