A Better Way to Test Your React-Redux App
Update: The approach described in this post is now the recommended way to test in the official Redux docs.
I come across a lot of React-Redux apps where components, action creators, selectors, and reducers are tested as separate units. It’s a common practice—it’s even described in the Redux docs (archived)—but there’s a better way.
In this post, I’ll show you why the standard approach for testing React-Redux apps is insufficient and makes refactoring harder. I’ll also show you an easier way to test your app that catches more bugs and keeps refactors safe.
Testing Atoms
So what’s the problem with testing components, action creators, selectors, and reducers separately?
First, testing these elements in isolation doesn’t guarantee that they work together. A unit test for an action creator asserts that an action is created but doesn’t verify that the action is ever dispatched. A unit test for a reducer asserts that a new state is returned but doesn’t verify that the UI updates to reflect it. There’s a disconnect.

Second, because these tests require you to mock other parts of the system, you lose confidence in the integration between what you’re testing and the dependency being mocked. For example, the Redux docs recommend redux-mock-store for async action creators. A mock store looks like a real Redux store, but its state is static. It lets you verify that certain actions are dispatched but tells you nothing about how those actions change real state.
Finally, these tests are so granular that refactoring becomes painful. A small change to one module often requires updates to several tests. This slows development and increases the chance of new bugs.
That’s what I call testing the atoms. Knowing that tiny chunks of code work in isolation is great, but to be confident they work together, test the molecules.
Testing Molecules
Components, action creators, selectors, and reducers are like atoms that combine to create a connected component molecule. Testing the molecule verifies the connections between its atoms.
Here’s an example:
// Component (atom)
import React from 'react'
const Counter = ({count, onClick}) => (
<button title="Click Me" onClick={onClick}>Count: {count}</button>
)
export default Counter
// Action creator (atom)
export const incrementCounter = () => ({
type: 'INCREMENT_COUNTER'
})
// Reducer (atom)
const counterReducer = (state = {count: 0}, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
return {
count: state.count + 1
}
default:
return state
}
}
export default counterReducer
// Connected component (molecule)
import {connect} from 'react-redux'
import {incrementCounter} from '/actions'
import Counter from './counter'
export default connect(
(state) => state,
{
onClick: incrementCounter
}
)(Counter)
The component renders a button that increments a counter when clicked.
Here’s what the molecule test looks like:
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import {render, getByTitle, fireEvent} from 'react-testing-library'
import 'react-testing-library/cleanup-after-each'
import 'jest-dom/extend-expect'
import Counter from '../'
import counterReducer from '/reducers/counter-reducer'
// Create a real redux store
const store = createStore(counterReducer, {
count: 0
})
it('increments the counter', () => {
const {container} = render(
<Provider store={store}>
<Counter />
</Provider>
)
const button = getByTitle(container, 'Click Me')
fireEvent.click(button)
expect(button).toHaveTextContent('Count: 1')
fireEvent.click(button)
expect(button).toHaveTextContent('Count: 2')
})
A few takeaways:
- The connected component is the only module under test, but the test covers every line inside the other modules. By testing a molecule, you indirectly test its atoms.
- The test verifies the connections between atoms. It fails if the component’s
onClickprop isn’t wired toincrementCounteror if the reducer mishandles theINCREMENT_COUNTERaction. - The test uses a real Redux store instead of redux-mock-store. Using a real store closes the loop between UI event (input) and UI update (output). You fire an event and assert that the UI updates. With a mock store, you can only assert that actions were dispatched.
Because the test focuses on behavior rather than implementation details, you can refactor the atoms without breaking it.
Many people assume integration tests are necessarily broad in scope, while they can be more effectively done with a narrower scope.
Narrow integration tests give you more confidence in the stability of your application because they verify the connections between smaller units of code. Instead of writing a unit test for every atom in your app, zoom out and write integration tests for the molecules.
Write tests. Not too many. Mostly integration.
There are plenty of scenarios where unit tests make sense (shared libraries, TDD, etc.) but for testing the overall behaviour of your application, integration tests are more likely to catch problems.
Summary
- Unit tests don’t cover the connections between components, action creators, selectors, and reducers.
- Integration tests give you more confidence in the stability of your application because they verify the relationships between units of code.
- Testing a connected component from the UI allows you to refactor its implementation freely.
- Integration tests don’t have to be wide in scope. You can use them to test the connection between just a handful of modules.
Discuss on Twitter • Edit on GitHub
Keep Reading
- I Built a Custom Event Router, Then Deleted It: Lessons in Knowing When to Stop Building
15 Feb 2025 - AI Generates Configuration, Not Code: How I Use LLMs to Build Product Capabilities
08 Feb 2025 - Schema-Driven Platforms: Why JSON Schema Is the Most Underrated Tool in Your Stack
01 Feb 2025 - Why Use a JavaScript Framework
13 Jul 2019 - Hacking Image Interpolation for Fun and Profit
13 Jan 2019 - The Perils of Jest Snapshot Testing
07 Jan 2019 - A Better Way to Test Your React-Redux App
01 Jan 2019 - Firebase + Create React App
01 Aug 2018 - Partial Application in Action
29 Sep 2017 - Using SVG to Shrink Your PNGs
07 Sep 2014 - Adaptive content everywhere
06 Feb 2013