Using Dependency Injection in React With Cypress Component Testing
Pass component dependencies as props or inject context through a custom Cypress mount command. Learn to configure providers, isolate state, and troubleshoot tests.
In Cypress Component Testing, dependency injection usually means supplying a component’s inputs directly as props or mounting it inside the React provider that supplies its context. Use props for ordinary component inputs such as a service function or callback. Use a provider wrapper when the component calls a context hook or depends on application state such as a Redux store. A custom cy.mount() command lets tests share the wrapper while still choosing test-specific values.
Cypress mounts the component in its component-testing browser environment. The test can then exercise rendered behavior and browser interactions. It does not require a separate dependency-injection container: use the seams already present in the component and application.
1. Choose the dependency seam
| Approach | Use it when | Trade-off |
|---|---|---|
| Pass a dependency as a prop | It is a normal component input, such as a callback, API adapter, or data value. | Explicit and local; may add a prop to the component API. |
| Wrap with a provider | The component consumes React context or needs application-level state. | Matches the app’s context; the mount helper needs suitable options and state isolation. |
Prefer the seam that matches normal application use. A focused prop can make a service easy to replace in a test, but do not turn every internal detail into a public prop. A context consumer should generally receive the provider its production code expects.
2. Inject a service through props
Define the service in the component’s props and pass a Cypress spy or stub in the test. This example is complete for a React component spec using Cypress’s React mount support:
// src/SaveButton.tsx
export type SaveButtonProps = {
save: () => Promise<void>
}
export function SaveButton({ save }: SaveButtonProps) {
return (
<button onClick={() => void save()}>Save</button>
)
}
// cypress/component/SaveButton.cy.tsx
import { SaveButton } from '../../src/SaveButton'
describe('SaveButton', () => {
it('calls the injected save service when clicked', () => {
const save = cy.stub().resolves()
cy.mount(<SaveButton save={save} />)
cy.contains('button', 'Save').click()
cy.wrap(save).should('have.been.calledOnce')
})
})
The service’s real implementation is not needed to verify that this button requests a save. If the component also displays loading or error states, make the injected function return a controlled promise or rejection and assert the resulting UI. Test the service implementation’s own parsing, retries, or network policy separately as unit logic where appropriate.
3. Wrap context consumers with a custom mount command
For context consumers, centralize recurring provider setup in Cypress’s custom mount command. The following TypeScript example uses Redux and a store factory. Adapt the import paths and types to the application. The store factory must create a new store when a test does not pass one explicitly.
// cypress/support/component.tsx
import { mount } from 'cypress/react'
import type { MountOptions, MountReturn } from 'cypress/react'
import type { ReactNode } from 'react'
import { Provider } from 'react-redux'
import type { AppStore } from '../../src/store'
import { makeStore } from '../../src/store'
type AppMountOptions = MountOptions & {
store?: AppStore
}
declare global {
namespace Cypress {
interface Chainable {
mount(
component: ReactNode,
options?: AppMountOptions,
): Chainable<MountReturn>
}
}
}
Cypress.Commands.add('mount', (component, options = {}) => {
const { store = makeStore(), ...mountOptions } = options
return mount(
<Provider store={store}>{component}</Provider>,
mountOptions,
)
})
export {}
Register the support file and use the custom command in a component test:
// cypress/support/component-index.html or the configured component support file
import './component'
// cypress/component/CartSummary.cy.tsx
import { CartSummary } from '../../src/CartSummary'
import { makeStore } from '../../src/store'
describe('CartSummary', () => {
it('renders the cart from the store', () => {
const store = makeStore({
cart: { items: [{ id: 'a', name: 'Notebook', quantity: 2 }] },
})
cy.mount(<CartSummary />, { store })
cy.contains('Notebook').should('be.visible')
})
})
The exact store factory signature and Cypress mount types depend on the project. Cypress documents custom mount commands, provider wrappers, props, router configuration, and Redux store examples in its React examples and mount command reference.
4. Make provider values configurable per test
A mount helper should accept only the options tests need to vary. For example, a router consumer may need a particular initial route, while a Redux consumer may need a prepared store. Cypress’s React examples show both kinds of configurable wrapper setup.
Keep defaults useful for ordinary cases, and let a test override them when route or state matters. Avoid hidden global state: a test should make its relevant starting state clear in its setup or fixture.
// Sketch of a router-aware mount helper option
type RouterMountOptions = {
initialEntries?: string[]
}
// In the custom mount command, read initialEntries from options,
// construct the router with those entries, and mount the component
// inside the router provider.
This is an option pattern rather than a drop-in router implementation: router packages and their APIs differ. Use the router provider and configuration supported by the version installed in your project.
5. Keep mutable state isolated
Create a fresh Redux store for each test. Reusing one mutable store across tests can let a dispatch in one test affect a later test, making results depend on execution order. A store factory provides a clean initial state, while passing a prepared store makes a particular test’s state explicit.
- Use a new store for every test that mounts a store consumer.
- Pass a prepared store when testing a specific state transition or initial state.
- Avoid module-level mutable stores shared by component specs.
- Keep fixtures small enough that the relevant state is clear.
This isolation principle applies to any mutable provider value, not only Redux: construct per-test state instead of mutating shared setup.
6. Configure and run Cypress Component Testing
Component tests use Cypress’s React mounting support and a component dev-server flow: Cypress compiles the component spec and support files, serves them, and renders the component in a browser. Choose the React integration and bundler configuration that matches the app. Cypress’s current React overview lists React 18 and 19 and documents Vite, Webpack, and Next.js setups; check the current React component testing overview and the project’s installed versions because compatibility details can change.
- Install and configure Cypress Component Testing for the project’s framework and bundler using Cypress’s current setup guide.
- Set the component support file to import the custom mount command.
- Import the application component into a
.cy.tsxspec and mount it with props or the required provider wrapper. - Run the component test runner or the project’s configured Cypress component test command.
For exact mount options and strict mode support, consult the Cypress React API reference. The development-server setup is described in component framework configuration.
7. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| A context hook throws that it must be used inside a provider. | The test mounted the component without its context provider. | Wrap it in the application’s provider, preferably through the custom mount command. |
| A test sees state left by another test. | A mutable store or provider value was shared. | Create fresh provider state per test; use a store factory and pass a test-specific store as needed. |
| The custom command rejects an option or reports a type mismatch. | The app-specific option type does not match the installed Cypress mount API or store type. | Check the current Cypress React API, define an options type that extends the installed mount options, and keep wrapper-only options out of the underlying mount call. |
| The component spec cannot resolve JSX, imports, or the dev server. | The component framework or bundler configuration does not match the application. | Review Cypress’s component framework configuration and align the React, bundler, and support-file setup with the project. |
| A stubbed service call occurs but the UI assertion races. | The component updates asynchronously after the promise resolves. | Assert the resulting DOM with Cypress retryable queries and assertions rather than reading it immediately after the click. |
| A test passes alone but fails in a suite. | Shared mutable state, order dependence, or incomplete cleanup is likely. | Make setup per test, avoid module-scoped state, and ensure each test states its needed provider values. |
8. Performance, reliability, and cost
Provider wrappers make repeated setup easier to maintain, but they do not replace deciding what behavior belongs in a component test. Use Cypress when the rendered component, browser DOM, or interaction is part of the behavior being verified. A pure dependency factory or transformation can be tested separately as ordinary unit logic. Keep the mount helper focused so each component test does not acquire unrelated application setup.
For reliable results, use deterministic service stubs and fresh mutable provider state. Avoid relying on external services for a component test’s basic outcome; those introduce network and availability variables unrelated to the UI behavior under test. Cypress runs the component in a browser through its dev-server workflow, so framework and bundler compatibility affect setup and execution.
Test execution cost depends on the project’s Cypress setup and infrastructure; the cited Cypress guidance does not establish a benchmark or price for this injection pattern. Dependency injection itself does not require purchasing a container library.
Or skip the browser setup
If you need screenshots of the website or component output as an artifact, ScreenshotNeo is a website screenshot API and MCP server. It does not replace Cypress component testing or inject React dependencies. Its API can capture a page with one request; see the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python and Node.js requests:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card.
FAQ
Should every service be injected as a prop?
No. Props fit dependencies that are natural component inputs. Context consumers should be mounted with their provider; avoid expanding a component’s public API for every internal detail.
Can a Cypress mount helper accept a different Redux store per test?
Yes. Give the helper a store option, default it to a newly created store, and pass a prepared store from tests that need specific initial state.
Do I need a dependency-injection container library?
Not for this Cypress pattern. Props and provider wrappers cover the approaches described here; use a container only if the application architecture independently calls for one.
Where should pure dependency logic be tested?
Test a pure factory or transformation as ordinary unit logic. Use Cypress component testing for behavior that depends on rendering and browser interactions.


