What do beforeEach/afterEach do, and why is shared state between tests dangerous?
Understanding why tests must not leak state into each other and how lifecycle hooks enforce isolation.
Lifecycle hooks run code around your tests: beforeEach/afterEach run before and after EVERY test in scope; beforeAll/afterAll run ONCE before and after the whole group. Their main job is to give each test a fresh, known starting world and to clean up after it — create a new instance, reset a store, mount a component, seed a fixture in beforeEach; unmount, clear mocks, restore globals in afterEach. The reason this matters is ISOLATION: tests must be independent, so that they pass or fail the same way whether run alone, together, or in any order. Shared mutable state between tests breaks that. If test A mutates a module-level object, a singleton, the DOM, localStorage, or a mock's call history, and test B silently depends on or is polluted by that, you get ORDER-DEPENDENT tests: they pass when run in one order and fail in another, or one test's failure cascades into others. That's a nightmare to debug because the failing test isn't the buggy one. The fixes: put fresh setup in beforeEach (not beforeAll, unless the resource is genuinely immutable and expensive) so nothing carries over; reset all mocks between tests (jest's clearMocks/resetMocks or afterEach(() => jest.clearAllMocks())); and reset shared browser state (localStorage, document body) if you touched it. Prefer creating new local objects inside each test over sharing a top-level `let` that tests mutate. A good litmus test: run the suite with randomized order (test runners support it) — if that surfaces failures, you have hidden shared state. The discipline pays off as the ability to run one test in isolation and trust its result, and to parallelize the suite safely.
Structuring test setup; debugging tests that only fail in a certain order or when run together.
let cart;
beforeEach(() => {
cart = createCart(); // fresh state per test -> no leakage
});
afterEach(() => {
jest.clearAllMocks(); // reset spy call history
localStorage.clear(); // reset shared browser state you touched
});
test('starts empty', () => expect(cart.items).toHaveLength(0));
test('adds an item', () => { cart.add(item); expect(cart.items).toHaveLength(1); });
// Because cart is rebuilt each time, order can't make one test affect another.