Real components rarely work in complete isolation — they fetch data from APIs, read from context providers, use routing hooks, and render child components. When you mount a component for testing, these dependencies are not available (there is no running app, no API server, no router). You must provide mocks or stubs for every external dependency so the component can render and behave as expected. This is the most technically challenging aspect of component testing, and getting it right determines whether your CT suite is maintainable or a constant source of setup headaches.
Stubbing External Dependencies in Component Tests
Each type of dependency requires a different stubbing approach.
// ── DEPENDENCY 1: API calls (fetch / axios) ──
// Component that fetches data on mount:
/*
function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch('/api/products').then(r => r.json()).then(setProducts);
}, []);
return products.map(p => <ProductCard key={p.id} {...p} />);
}
*/
// Test: stub the API call with cy.intercept (works in CT too!)
it('should render products from API', () => {
cy.intercept('GET', '/api/products', {
body: [
{ id: 1, name: 'Widget', price: 9.99, inStock: true },
{ id: 2, name: 'Gadget', price: 19.99, inStock: false },
],
}).as('getProducts');
cy.mount( );
cy.wait('@getProducts');
cy.get('[data-cy="product-card"]').should('have.length', 2);
});
// ── DEPENDENCY 2: React Context / Providers ──
// Component that reads from a theme context:
/*
function ThemedButton({ label }) {
const { primaryColor } = useTheme(); // reads from ThemeContext
return <button style={{ backgroundColor: primaryColor }}>{label}</button>
}
*/
// Test: wrap the component with the required provider
it('should render with theme color from context', () => {
const mockTheme = { primaryColor: '#3B82F6', darkMode: false };
cy.mount(
);
cy.get('button')
.should('have.text', 'Click Me')
.and('have.css', 'background-color', 'rgb(59, 130, 246)');
});
// ── DEPENDENCY 3: React Router ──
// Component that uses useNavigate or Link:
/*
function NavBar() {
const navigate = useNavigate();
return <button onClick={() => navigate('/settings')}>Settings</button>
}
*/
// Test: wrap with MemoryRouter
import { MemoryRouter } from 'react-router-dom';
it('should render navigation links', () => {
cy.mount(
);
cy.get('button').contains('Settings').should('be.visible');
});
// ── DEPENDENCY 4: Global state (Redux, Zustand) ──
// Wrap with a Provider and pass a mock store
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from '../../store/cartSlice';
it('should display cart item count from Redux store', () => {
const mockStore = configureStore({
reducer: { cart: cartReducer },
preloadedState: {
cart: { items: [{ id: 1, name: 'Widget', qty: 2 }], total: 19.98 },
},
});
cy.mount(
);
cy.get('[data-cy="cart-count"]').should('have.text', '1');
cy.get('[data-cy="cart-total"]').should('contain.text', '$19.98');
});
// ── DEPENDENCY 5: Creating a reusable mount wrapper ──
// cypress/support/component.ts — custom mount with providers
/*
import { mount } from 'cypress/react18';
import { ThemeProvider } from '../src/context/ThemeContext';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createMockStore } from '../src/test-utils/mockStore';
function mountWithProviders(component, options = {}) {
const { route = '/', storeState = {} } = options;
return mount(
<Provider store={createMockStore(storeState)}>
<ThemeProvider>
<MemoryRouter initialEntries={[route]}>
{component}
</MemoryRouter>
</ThemeProvider>
</Provider>
);
}
Cypress.Commands.add('mount', mountWithProviders);
*/
// Usage in tests — clean, no wrapper boilerplate:
// cy.mount(<ProductList />);
// cy.mount(<OrderPage />, { route: '/orders/123', storeState: { auth: { user: 'alice' } } });
cy.mount command with a version that automatically wraps components in all required providers (Theme, Router, Store), you eliminate boilerplate from every component test. Without it, every test file repeats 10-15 lines of provider wrapping. With it, tests call cy.mount(<MyComponent />) and the providers are handled automatically. This is the component testing equivalent of the E2E conftest.py fixture pattern.cy.intercept() works in component tests exactly as it does in E2E tests. If your component makes API calls (via fetch, axios, or any HTTP client), stub them with cy.intercept() before mounting. This is the simplest and most consistent way to provide fake API data to a component — no need to mock fetch at the module level or inject fake HTTP clients through props.cy.mount(<Component />, { auth: false }) should skip the auth provider. Flexibility in the mount wrapper is as important as convenience.Common Mistakes
Mistake 1 — Forgetting to wrap components that use Context or Router
❌ Wrong: cy.mount(<NavBar />) — crashes with “useNavigate() may only be used within a Router” because no Router is provided.
✅ Correct: cy.mount(<MemoryRouter><NavBar /></MemoryRouter>) or use a custom mount wrapper that includes the router automatically.
Mistake 2 — Over-mocking dependencies instead of using real implementations
❌ Wrong: Mocking every child component, every hook, and every utility function — the test verifies mock behaviour, not component behaviour.
✅ Correct: Mock only external boundaries (API calls, browser APIs, third-party services). Let internal child components, hooks, and utilities run for real. The more real code executes, the more defects the test catches.