QA How-To
How to Use Cypress component testing (2026)
Learn cypress component testing in 2026 with React setup, typed mounting, providers, network stubs, accessibility checks, debugging, and CI practices.
23 min read | 3,076 words
TL;DR
Configure Cypress Component Testing with the supported framework adapter and development bundler, register a typed `cy.mount`, and build components with explicit providers and controlled external boundaries. Test real browser behavior at this layer, while leaving pure logic to unit tests and critical deployed journeys to end-to-end tests.
Key Takeaways
- Use Cypress Launchpad to detect the supported framework and bundler, then review and commit the explicit configuration.
- Mount realistic component trees and assert behavior through props, user interaction, callbacks, and visible states.
- Keep fresh providers, stores, and data clients per test to prevent cache and state leakage.
- Register intercepts before mount when initialization sends requests, and treat fixtures as UI contracts only.
- Prefer semantic selectors and test keyboard, focus, loading, empty, error, and responsive behavior intentionally.
- Keep pure logic in unit tests and retain selected end-to-end journeys for deployed integration confidence.
- Run component tests as a locked, artifact-rich pull-request signal and optimize trust rather than test count.
Cypress component testing runs a UI component in a real browser with a development bundler, then gives the test Cypress commands, retryable queries, network control, and interactive debugging. It is most useful when a component has meaningful rendering and interaction behavior but a full end-to-end deployment would make feedback slower and failures less precise.
In 2026, the durable setup is to let the Cypress Launchpad scaffold the adapter for your supported framework and bundler, keep the generated configuration explicit, and test components through public inputs and user-visible outcomes. The examples below use React, TypeScript, and Vite, but the design principles apply to other frameworks that Cypress officially supports.
This guide covers architecture, installation, mounting, selectors, state, providers, network requests, accessibility, CI, and maintenance. It also explains when a component test is the wrong layer, which is as important as knowing the API.
TL;DR
| Question | Practical answer |
|---|---|
| What runs? | One component tree mounted in a real browser |
| What is controlled? | Props, providers, browser APIs, and network boundaries |
| What should be asserted? | User-visible behavior and meaningful integration contracts |
| What should stay real? | Rendering, events, local state, and valuable child composition |
| What should be replaced? | Slow, unsafe, or nondeterministic external boundaries |
| What is the main risk? | Testing a mocked fantasy or component internals |
1. What Cypress Component Testing Is and Is Not
Cypress component testing mounts a component and its child tree on a dedicated test page. Cypress still controls a real browser, so the component uses the browser rendering engine, DOM, events, focus model, CSS, and application JavaScript. It does not require the complete application to be deployed or navigated from its home page.
This layer sits between small logic tests and broad end-to-end journeys. It can prove that a form validates and submits, a menu supports keyboard use, a data grid renders service states, or a provider-backed widget changes behavior. Failures are usually easier to localize because the test constructs the relevant component state directly.
It is not a replacement for all unit or end-to-end tests. A pure currency function is faster and clearer in a unit runner. A checkout that depends on real routing, authentication, deployed services, cookies, and payment integration still needs selected end-to-end evidence. Component tests reduce the number of scenarios that require that expensive path.
The mount is a real integration boundary. If the component imports children, styles, hooks, and utilities, those run unless you deliberately replace them. This makes the test valuable, but it also means a broken bundler alias or missing global style can fail the component before an assertion executes.
Use the primary keyword as a design question: does Cypress component testing give the quickest trustworthy proof for this behavior? Choose it when browser rendering matters, state can be constructed locally, and full-system dependencies would add cost without improving the oracle.
2. Cypress Component Testing Versus Unit and End-to-End Tests
Test layers are complementary. The best layer is the lowest practical boundary that observes the guarantee without mocking away what matters.
| Characteristic | Logic unit test | Cypress component test | Cypress end-to-end test |
|---|---|---|---|
| Runtime boundary | Function or small module | Mounted component tree | Deployed application journey |
| Browser rendering | Usually simulated or absent | Real browser | Real browser |
| State setup | Direct values and fakes | Props, providers, intercepts | UI, API, database, or seeded environment |
| Failure scope | Very narrow | Component or local integration | Multiple services and deployment layers |
| Best for | Algorithms and edge combinations | Rendering, interaction, component states | Critical integrated user paths |
| Common overuse | Testing implementation details | Mocking every child and hook | Repeating all business combinations |
Suppose a discount banner has pure eligibility logic, visual variants, and a checkout dependency. Test the calculation with unit cases. Test rendering, dismissal, focus, and accessible text as a component. Keep one end-to-end path that confirms the real checkout supplies the correct eligibility and honors the selected offer.
Component tests can also replace brittle page-level setup. Instead of logging in, navigating through five screens, and manufacturing a backend state to reach an empty table, mount the table with an empty response or a controlled request. The test becomes both faster and more explicit.
Do not choose the layer by tool enthusiasm. Ask what can fail, who owns the contract, and what observation proves it. If a mocked provider returns an impossible state, a green component test is misleading. If an end-to-end test fails because an unrelated login service is down, it provides poor feedback for a button style rule.
For a broader portfolio decision, review the practical test pyramid for automation.
3. Install and Configure Cypress Component Testing
Start with a working application and use the Cypress Launchpad when possible. Run npx cypress open, choose Component Testing, and select the detected framework and bundler. Cypress can create or update the configuration and support file. Review every generated change and commit it. Detection avoids guessing adapter versions, while explicit files keep CI reproducible.
For a React, TypeScript, and Vite project, install Cypress and the React adapter as development dependencies. If the application already uses Vite and React, its Vite packages are normally present:
npm install --save-dev cypress
npx cypress open
A minimal configuration uses the public defineConfig API:
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.{ts,tsx}',
},
})
The exact generated dev-server options can vary with supported framework and bundler combinations. Keep the configuration that Cypress scaffolds for your actual project instead of copying React settings into Vue, Angular, or a custom webpack application.
Place the component support file at the path shown in the Launchpad. It is the right place for the mount command, global styles, framework plugins, and narrowly scoped browser setup. Avoid loading production analytics or unrelated application bootstrap code there.
Use npx cypress open --component for interactive work and npx cypress run --component for headless execution. Keep dependency versions in the lockfile, and run setup from a clean clone before calling the configuration complete.
4. Register a Typed Mount Command and Global Styles
The adapter's mount function renders JSX into the Cypress component test page. Registering it as cy.mount makes specs readable and gives you one extension point for providers. Type the command so editors and the TypeScript compiler understand its arguments and return value.
// cypress/support/component.ts
import { mount } from 'cypress/react'
import '../../src/index.css'
Cypress.Commands.add('mount', mount)
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
export {}
This declaration augments Cypress's global types. The export {} makes the file a module so global augmentation works consistently. If your project keeps declarations in a separate .d.ts file, include that file in the TypeScript configuration used for Cypress.
Import global CSS deliberately. Component tests do not necessarily pass through the same application entry point as production, so typography, design tokens, resets, or utility styles may otherwise be absent. If a component imports its own CSS module, the bundler follows that import normally.
Do not immediately turn the mount command into a universal application harness with dozens of implicit providers. Hidden wrappers make a test's state hard to understand. Provide small named helpers such as mountWithTheme or options for the few global contexts every component genuinely needs.
After mounting, use Cypress queries and assertions. Cypress automatically retries built-in query chains such as cy.get(...).should(...) and cy.contains(...).should(...). A project can add Cypress Testing Library separately when its accessible queries are preferred. Do not store an element in a plain variable and expect Cypress retry semantics to follow it.
5. Write the First Behavior-Focused Component Spec
Consider a status badge that maps domain state to visible text and an accessible label. The component has no network dependency, but browser rendering and accessibility are valuable. The test should express outcomes, not internal state or class implementation.
// src/components/StatusBadge.tsx
type Status = 'queued' | 'running' | 'complete'
export function StatusBadge({ status }: { status: Status }) {
const labels: Record<Status, string> = {
queued: 'Queued',
running: 'In progress',
complete: 'Complete',
}
return (
<span data-status={status} aria-label={`Build status: ${labels[status]}`}>
{labels[status]}
</span>
)
}
// src/components/StatusBadge.cy.tsx
import { StatusBadge } from './StatusBadge'
describe('StatusBadge', () => {
;(['queued', 'running', 'complete'] as const).forEach((status) => {
it(`renders the ${status} state`, () => {
cy.mount(<StatusBadge status={status} />)
const expected = {
queued: 'Queued',
running: 'In progress',
complete: 'Complete',
}[status]
cy.contains(expected)
.should('be.visible')
.and('have.attr', 'data-status', status)
.and('have.attr', 'aria-label', `Build status: ${expected}`)
})
})
})
This is runnable in a configured React component project. The semicolon before the array avoids automatic semicolon insertion ambiguity after the describe callback line in semicolon-free styles.
Prefer accessible roles, names, labels, and visible content when they uniquely identify behavior. A data-* attribute is appropriate when it represents a stable test or domain contract. Avoid .badge > span:nth-child(2) because it couples the test to layout.
Do not assert every CSS class. Assert the visual or semantic state the user relies on, such as visibility, enabled state, accessible name, data contract, or a small computed style when appearance is itself the requirement.
6. Test Interaction, Props, Events, and Local State
Interactive components should be driven as a user would drive them. Click controls, type into inputs, use keyboard navigation, and assert visible state or callback contracts. Avoid calling internal component methods or mutating framework state from the test.
A controlled quantity selector can expose a callback. Cypress stubs provide a precise way to observe that boundary:
// src/components/QuantityPicker.cy.tsx
import { QuantityPicker } from './QuantityPicker'
describe('QuantityPicker', () => {
it('reports the next allowed quantity', () => {
const onChange = cy.stub().as('onChange')
cy.mount(
<QuantityPicker value={2} min={1} max={3} onChange={onChange} />,
)
cy.get('button[aria-label="Increase quantity"]').click()
cy.get('@onChange').should('have.been.calledOnceWith', 3)
})
it('disables increase at the maximum', () => {
cy.mount(
<QuantityPicker value={3} min={1} max={3} onChange={cy.stub()} />,
)
cy.get('button[aria-label="Increase quantity"]').should('be.disabled')
})
})
The buttons expose explicit accessible names, so built-in attribute queries are deterministic in this standalone example. A team can use findByRole after installing and registering Cypress Testing Library. Never paste a plugin command into a repository without its real dependency and support setup.
For state transitions, assert before and after only when the initial state matters to the contract. A test can confirm that an accordion starts collapsed, expands with a click, moves focus as required, and collapses with the keyboard. Keep each test centered on one behavioral rule while allowing multiple assertions that prove that rule.
Use real timers by default. When a component intentionally depends on a debounce, countdown, or interval, cy.clock() and cy.tick() can control browser time. Install the clock before mounting if initialization schedules the timer. Do not fake time when the defect depends on actual animation or browser scheduling.
7. Mount Components With Providers, Routing, and Context
Many components require a router, theme, localization, state store, or data client. Wrap only the dependencies required for the behavior. A named mount helper can make these dependencies explicit while preserving a single place for construction and cleanup.
// cypress/support/mountWithRouter.tsx
import type { ReactNode } from 'react'
import { MemoryRouter } from 'react-router-dom'
export function RouterHarness({
children,
initialPath = '/',
}: {
children: ReactNode
initialPath?: string
}) {
return (
<MemoryRouter initialEntries={[initialPath]}>
{children}
</MemoryRouter>
)
}
A spec can mount <RouterHarness initialPath="/orders/42"> around the subject. Memory routing is appropriate when the test is about component routing behavior and does not need the deployed server. An end-to-end test is still needed for server rewrites, authentication redirects, and real navigation integration.
Create a fresh state store or query client for each test. Reusing one singleton allows cached data and mutations to leak between cases. Configure retries and cache behavior for deterministic tests, but do not make the test environment so different that it can represent states production never creates.
Providers deserve tests too. A custom render helper should have one small smoke spec that proves it supplies theme, router, and other required context. When a global provider changes, that contract test gives a direct error instead of dozens of mysterious component failures.
If a provider talks to a service, decide whether the component test should use cy.intercept, inject a fake adapter, or run a real local boundary. Use intercepts for browser HTTP behavior, injected fakes for a designed application port, and a real service only when the integration is the behavior under test.
8. Control Network Requests Without Testing a Fantasy
cy.intercept() can observe or stub browser requests made by a mounted component. Register the intercept before mounting when the component requests data during initialization. Give the route an alias and wait for the exact request before asserting the loaded state.
// src/components/ProfileCard.cy.tsx
import { ProfileCard } from './ProfileCard'
describe('ProfileCard', () => {
it('shows a customer returned by the service', () => {
cy.intercept('GET', '/api/customers/42', {
statusCode: 200,
body: { id: '42', name: 'Ada Rivera', tier: 'gold' },
}).as('getCustomer')
cy.mount(<ProfileCard customerId="42" />)
cy.wait('@getCustomer')
.its('request.url')
.should('include', '/api/customers/42')
cy.contains('h2', 'Ada Rivera').should('be.visible')
cy.contains('Gold member').should('be.visible')
})
it('offers retry after a service failure', () => {
cy.intercept('GET', '/api/customers/42', {
statusCode: 503,
body: { code: 'TEMPORARILY_UNAVAILABLE' },
}).as('failedCustomer')
cy.mount(<ProfileCard customerId="42" />)
cy.wait('@failedCustomer')
cy.get('[role="alert"]').should('contain.text', 'Try again')
})
})
Match routes narrowly enough to catch wrong paths and query values. Assert the outgoing method, URL, headers, or body when the request contract belongs to the component. Do not reproduce the entire backend in fixtures. Use representative success, empty, permission, validation, and recoverable failure responses based on a versioned contract.
An intercept cannot prove the real provider honors the schema or business rules. Cover that with contract and API tests. Component tests prove how the UI sends a request and handles documented responses. Read API contract testing fundamentals for the complementary layer.
9. Cover Accessibility, Visual States, and Responsive Behavior
Accessibility should influence queries and assertions from the start. A button needs an accessible name. An input needs an associated label. An error that requires immediate attention may need an alert or live-region behavior. Keyboard interaction and focus order are browser behavior, which makes component tests a good fit.
Test the interaction contract directly. Tab to a control, activate it with the supported key, assert focus moves to the intended element, and verify dismissal restores focus when required. Do not infer accessibility from the presence of ARIA attributes alone. Native HTML semantics are usually preferable to custom roles.
Automated accessibility scans can detect a useful subset of problems if you install and configure a real integration such as cypress-axe. They do not prove that wording, reading order, keyboard workflow, or product context is usable. Keep manual review and assistive-technology testing in the strategy.
For responsive behavior, set the component test viewport with cy.viewport(width, height) and assert behavior at meaningful layout boundaries. Avoid duplicating every device preset unless the component contract changes at each one. If container queries determine layout, ensure the mount container provides the relevant size instead of assuming viewport width alone controls it.
Visual regression can catch spacing, wrapping, color, and icon drift, but screenshot commands and services depend on the selected visual tool. Do not invent a cy.visualSnapshot command. Install a supported plugin or service, document its command, control fonts and animations, and review baselines as code.
Test visible states intentionally: default, hover or focus where relevant, loading, empty, long content, error, disabled, high contrast requirements, and narrow width. The point is not a screenshot gallery. It is risk coverage for states users can actually reach.
10. Run Cypress Component Testing in CI and Scale It
Use the same package-lock installation and component command locally and in CI. A minimal GitHub Actions job for a Vite application can install dependencies and run component specs. Pin action revisions according to your organization's dependency policy:
name: component-tests
on:
pull_request:
jobs:
cypress-component:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx cypress run --component --browser chrome
The development bundler starts as part of component test execution, so a separate deployed application server is generally unnecessary. If components depend on generated files, environment variables, fonts, or local services, make those prerequisites explicit and fail early.
Store screenshots and videos for failed CI runs according to your Cypress configuration and CI artifact policy. Include application revision, Cypress configuration, browser version, and shard identity in diagnostics. Protect secrets because request logs and screenshots can expose them.
Split work only after measuring. Group specs by stable ownership or use the orchestration method your CI and Cypress plan supports. Every shard must receive the same configuration and independent resources. A faster wall clock is not useful if workers share a cache or test account and create intermittent failures.
Track first-attempt reliability, runtime distribution, failure category, and defects detected at this layer. Do not optimize for test count. Delete obsolete scenarios, move pure logic downward, and keep a small end-to-end set upward. The suite should remain a fast, trusted pull-request signal.
Interview Questions and Answers
Q1: What is Cypress component testing?
It mounts a component tree in a real browser through the project's development bundler. Tests control props and external boundaries, interact through the DOM, and assert user-visible behavior. It provides narrower and usually faster feedback than a deployed end-to-end journey.
Q2: How is a component test different from an end-to-end test?
A component test constructs one UI boundary directly and often controls network responses. An end-to-end test navigates the deployed system with real integration paths. I use component tests for state combinations and interaction, then retain selected end-to-end journeys for deployment and cross-service confidence.
Q3: Does Cypress component testing use jsdom?
No. Cypress runs the mounted component in a real browser. The development bundler compiles the component and Cypress controls browser interaction, which provides real DOM, CSS, event, and layout behavior.
Q4: When should you register an intercept?
Register it before the action that triggers the request. If the component fetches during mount, define cy.intercept() before cy.mount(). Alias the route and wait for the request so the test synchronizes on a meaningful event.
Q5: How do you test a component that needs context providers?
I wrap it with the smallest provider harness that represents a valid state. Every test gets fresh provider instances, especially stores and query clients. Named helpers keep the dependency visible and avoid a universal hidden wrapper.
Q6: What selectors are best in component tests?
Accessible roles, names, labels, and visible content are strong when they uniquely represent user behavior. A stable data attribute is appropriate for an explicit test contract. I avoid DOM position and styling selectors because harmless refactoring breaks them.
Q7: Can component tests replace unit tests?
Not completely. Pure functions and exhaustive logic combinations are usually clearer and faster in a unit runner. Component tests add value when rendering, browser interaction, focus, CSS, or component integration is part of the guarantee.
Q8: How do you prevent state leakage?
Cypress remounts for tests, but application singletons can still leak. I create fresh stores, clients, fake data, and browser state per test and avoid module-level mutable values. Cleanup is narrow and owned by the case.
Common Mistakes
- Copying a React dev-server configuration into an unsupported framework or bundler combination.
- Forgetting global CSS, fonts, aliases, or provider setup that production supplies through its entry point.
- Mounting a component after the initialization request has already escaped the intercept.
- Testing component internals, hook state, or CSS class lists instead of user behavior.
- Mocking children and services until the rendered state is impossible in production.
- Sharing a query client or store between tests.
- Using arbitrary waits instead of retryable queries and aliased requests.
- Adding Testing Library commands without installing and registering the integration.
- Treating an intercept fixture as proof that the real API honors its contract.
- Using viewport presets without asserting a real responsive behavior change.
- Inventing plugin commands in examples without documenting the dependency.
- Running every business rule at the component layer when a unit test is more precise.
- Removing all end-to-end coverage because component tests are faster.
Conclusion
Cypress component testing gives QA and frontend teams a strong browser-based layer for rendering, interaction, accessibility, local integration, and response-state coverage. Configure it through the supported framework adapter, mount realistic component trees, control only external boundaries, and assert behavior through the public UI contract.
Start with one component that currently requires expensive end-to-end setup. Cover its critical success, empty, error, and keyboard states, then compare feedback precision. Keep pure logic below it and a few integrated journeys above it. That balanced portfolio is where component testing delivers lasting value.
Interview Questions and Answers
What is Cypress Component Testing?
It mounts a UI component tree in a real browser with the project development bundler. The test controls inputs and external boundaries and interacts through the DOM. It gives narrower feedback than a full deployed journey.
How does component testing differ from end-to-end testing?
A component test constructs one UI boundary directly, often with controlled responses and providers. An end-to-end test navigates a deployed application and crosses real integration boundaries. I use both for different confidence goals.
When should a behavior stay in a unit test?
Pure functions and exhaustive logic combinations usually belong in a unit runner. They do not need browser rendering or interaction. A component test adds value when DOM, focus, CSS, events, or child composition matter.
Why register an intercept before mounting?
A component effect may request data immediately during mount. A later intercept can miss that request and make the test nondeterministic. Registration order establishes the controlled boundary before side effects begin.
How do you prevent provider state leakage?
I create a fresh store, router, and data client for every test. I avoid module singletons and clear any deliberate browser state. The harness exposes important configuration instead of hiding it globally.
Which selectors do you prefer?
I prefer semantic elements, accessible roles, names, labels, and visible content when they uniquely describe behavior. Stable data attributes work for an explicit test contract. DOM position and styling selectors are fragile.
What does an intercepted test fail to prove?
It does not prove the real provider enforces authorization, returns the fixture, or persists state correctly. It proves the UI request and handling of a documented response. Contract and API tests cover the provider side.
How do you run component tests reliably in CI?
I install locked dependencies and run the same component command used locally. CI records browser, revision, configuration, and safe first-failure artifacts. Any parallel workers receive isolated resources and identical prerequisites.
Frequently Asked Questions
What is Cypress component testing?
It mounts a component tree in a real browser through the project development bundler. Tests control props and external boundaries, interact with rendered DOM, and assert user-visible behavior without deploying the whole application.
Does Cypress component testing use a real browser?
Yes. The component uses a real browser rendering engine, DOM, events, focus model, and CSS, while Cypress provides commands, assertions, and debugging.
How do I run Cypress component tests?
Use `npx cypress open --component` for interactive development and `npx cypress run --component` for headless execution. The project must have a supported framework and bundler configuration.
Can Cypress component testing replace end-to-end testing?
No. Component tests efficiently cover rendering and local integration states, but they do not prove deployed routing, real authentication, service compatibility, or cross-system journeys.
Can I use cy.intercept in a component test?
Yes, for browser requests made by the mounted component. Register the intercept before mount when initialization sends the request, alias it, and wait for the exact route.
How do I mount a component with providers?
Wrap the subject with the smallest valid router, theme, store, localization, or data-client harness. Create fresh provider instances for every test so state and cache cannot leak.
Where should Cypress component specs live?
Colocation beside components works well when it matches code ownership, while a dedicated tree can fit centralized test ownership. Use one consistent convention and a component-only spec pattern.