How to Test React Context API
This article provides a straightforward guide on how to test the Context API in React applications using React Testing Library and Jest. You will learn how to write unit tests for both context providers and consumers, ensuring your global state management remains robust and behaves as expected.
Setting Up the Context
To demonstrate how to test the Context API, let’s first look at a
simple ThemeContext example. This context provides a
theme value (“light” or “dark”) and a
toggleTheme function to switch between them.
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);Below is a consumer component, ThemeButton, which uses
this context to display the current theme and trigger the toggle
function.
import React from 'react';
import { useTheme } from './ThemeContext';
export const ThemeButton = () => {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
};Testing the Consumer Component
When testing a component that consumes a context, you must wrap the
component under test in the corresponding context provider. Otherwise,
the hook will return undefined, causing the test to
fail.
Here is how you test the ThemeButton using React Testing
Library:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import { ThemeButton } from './ThemeButton';
describe('ThemeButton Component', () => {
test('renders with the default context value', () => {
render(
<ThemeProvider>
<ThemeButton />
</ThemeProvider>
);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Current theme: light');
});
test('updates the context value when clicked', () => {
render(
<ThemeProvider>
<ThemeButton />
</ThemeProvider>
);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(button).toHaveTextContent('Current theme: dark');
});
});Testing with Custom Context Values
Sometimes you want to test how a consumer component behaves with specific context values without relying on the actual provider’s internal state logic. You can achieve this by rendering the component inside a mock provider.
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ThemeButton } from './ThemeButton';
import { ThemeContext } from './ThemeContext'; // Ensure ThemeContext is exported
test('renders correctly with dark theme using a mock provider', () => {
const mockValue = {
theme: 'dark',
toggleTheme: jest.fn(),
};
render(
<ThemeContext.Provider value={mockValue}>
<ThemeButton />
</ThemeContext.Provider>
);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Current theme: dark');
});Creating a Custom Render Utility
If you have many components that rely on the same context, wrapping them manually in every test introduces a lot of boilerplate. You can create a custom render function to automatically wrap components in the required providers.
import React from 'react';
import { render as rtlRender } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
const renderWithTheme = (ui, options) => {
const Wrapper = ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
);
return rtlRender(ui, { wrapper: Wrapper, ...options });
};
// Export everything from React Testing Library plus our custom render
export * from '@testing-library/react';
export { renderWithTheme as render };You can then import this custom render function in your test files to simplify your tests:
import React from 'react';
import { render, screen } from './test-utils'; // Path to your custom render utility
import { ThemeButton } from './ThemeButton';
test('renders with theme context using custom render utility', () => {
render(<ThemeButton />);
expect(screen.getByRole('button')).toHaveTextContent('Current theme: light');
});