Unit testing verifies small pieces of application logic in isolation. In a React application this commonly means testing utility functions, reducers, selectors, validation rules, hooks, and business logic without rendering the complete application. A strong test suite checks behavior rather than implementation details.
What should be unit tested?
Good unit-test candidates are deterministic functions with clear inputs and outputs. Examples include price calculations, permission checks, reducers, selectors, date formatting, validation rules, and business rules.
export function calculateTotal(items) {
return items.reduce(
(total, item) => total + item.price * item.quantity,
0
);
}
Jest example
import { describe, it, expect } from "@jest/globals";
import { calculateTotal } from "./cart";
describe("calculateTotal", () => {
it("calculates the cart total", () => {
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
];
expect(calculateTotal(items)).toBe(250);
});
});
Vitest example
import { describe, it, expect } from "vitest";
describe("calculateTotal", () => {
it("calculates total", () => {
expect(calculateTotal([
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
])).toBe(250);
});
});
Arrange, Act, Assert
A readable unit test normally prepares data, executes the behavior, and verifies the result.
// Arrange
const input = { price: 100, quantity: 3 };
// Act
const result = calculateLineTotal(input);
// Assert
expect(result).toBe(300);
Testing Redux reducers
const initialState = {
items: []
};
it("adds a product", () => {
const state = cartReducer(
initialState,
addItem({
productId: 10,
price: 100,
quantity: 1
})
);
expect(state.items).toHaveLength(1);
expect(state.items[0].productId).toBe(10);
});
Mocking a dependency
Unit tests can replace a dependency with a controlled mock when the dependency is outside the behavior being tested.
const paymentService = {
charge: vi.fn()
};
paymentService.charge.mockResolvedValue({
transactionId: "TX100"
});
Good and bad unit tests
| Good test | Problematic test |
|---|---|
| Tests business behavior | Tests private implementation details |
| Small and deterministic | Depends on real network |
| Fast | Depends on timers and external services |
| Clear failure message | Tests many unrelated behaviors |
Complete output
Test Files 1 passed
Tests:
✓ calculates the cart total
✓ handles an empty cart
✓ adds a product to the reducer
Result:
3 passed, 0 failed
Common mistakes
- Testing implementation details instead of observable behavior.
- Making unit tests call real APIs.
- Writing one enormous test for an entire feature.
- Using mocks everywhere even when a real simple function is safer.
- Ignoring edge cases such as empty arrays and invalid values.
Interview questions
- What is a unit test?
- Jest vs Vitest?
- What should be mocked in a unit test?
- What is Arrange-Act-Assert?
- How would you test a Redux reducer?
- Why should tests avoid implementation details?