How to Test Redux Selectors in React
Testing Redux selectors is a crucial step in ensuring your React application’s state management remains predictable and efficient. This article provides a straightforward guide on how to write unit tests for both basic and memoized Redux selectors using Jest. You will learn how to mock the Redux state, assert selector outputs, and isolate selector logic to keep your test suite fast and reliable.
Understanding Redux Selectors
Selectors are pure functions used to extract, format, or compute pieces of state from the Redux store. Because they are pure functions, testing them is highly straightforward: you pass a mock state object as an argument and assert that the selector returns the expected output.
Testing Basic Selectors
A basic selector retrieves a value directly from the state without any complex transformation.
Suppose you have the following basic selector:
// selectors.js
export const selectUserEmail = (state) => state.user.email;To test this selector, you only need to construct a mock state object that mirrors the structure of your Redux store and pass it to the function.
// selectors.test.js
import { selectUserEmail } from './selectors';
describe('selectUserEmail', () => {
it('should return the user email from the state', () => {
const mockState = {
user: {
email: 'user@example.com',
name: 'John Doe'
}
};
const result = selectUserEmail(mockState);
expect(result).toEqual('user@example.com');
});
it('should handle undefined state gracefully if safety checks are implemented', () => {
const mockState = { user: {} };
const result = selectUserEmail(mockState);
expect(result).toBeUndefined();
});
});Testing Memoized Selectors (Reselect)
Memoized selectors, often created using the reselect
library’s createSelector function, combine multiple input
selectors to compute derived state. Testing these follows the same
pattern as basic selectors, but you should also verify that the
underlying logic/computation works correctly.
Suppose you have the following memoized selector:
// selectors.js
import { createSelector } from 'reselect';
const selectItems = (state) => state.cart.items;
export const selectDiscount = (state) => state.cart.discount;
export const selectCartTotal = createSelector(
[selectItems, selectDiscount],
(items, discount) => {
const total = items.reduce((sum, item) => sum + item.price, 0);
return total - discount;
}
);Method 1: Testing with Mock State
You can test the selector by passing a mock state object, just like a basic selector. This ensures the entire selector chain works together.
// selectors.test.js
import { selectCartTotal } from './selectors';
describe('selectCartTotal', () => {
it('should calculate the total price of items minus the discount', () => {
const mockState = {
cart: {
items: [
{ id: 1, price: 10 },
{ id: 2, price: 20 }
],
discount: 5
}
};
const result = selectCartTotal(mockState);
expect(result).toEqual(25); // (10 + 20) - 5
});
});Method 2: Testing the Composed Function Directly (.resultFunc)
If your state structure is deeply nested or complex, you might want
to test the computation logic of a memoized selector in isolation.
Libraries like Reselect expose a .resultFunc property on
the created selector. This allows you to pass the inputs directly to the
projection function, bypassing the input selectors and the mock state
entirely.
// selectors.test.js
import { selectCartTotal } from './selectors';
describe('selectCartTotal logic isolation', () => {
it('should correctly calculate the total given mocked input values', () => {
const mockItems = [{ price: 50 }, { price: 30 }];
const mockDiscount = 10;
// Use .resultFunc to pass dependencies directly as arguments
const result = selectCartTotal.resultFunc(mockItems, mockDiscount);
expect(result).toEqual(70); // (50 + 30) - 10
});
});Using .resultFunc is highly recommended for complex
selectors, as it decouples your selector logic tests from the shape of
your overall Redux state tree.