Setup and teardown is the code around a test: something to prepare before, something to clean up after. Jest gives four hooks. beforeAll runs once before all the tests in the block, afterAll once after all of them; beforeEach before every test, afterEach after every one. The whole point is frequency. In the checkout-service example: a connection to the test database is expensive to spin up, so it is opened once in beforeAll; the cart every test must receive fresh, so it is assembled in beforeEach.
| Hook | When it runs | What for |
|---|---|---|
| beforeAll | once before all tests in the block | expensive immutable resource: DB connection, container |
| beforeEach | before each test | fresh mutable state: cart, user record |
| afterEach | after each test | undo what beforeEach set up |
| afterAll | once after all tests | close what beforeAll opened |
The rule is simple: into beforeAll goes the expensive and immutable - a database connection, a spun-up container, a read fixture. Into beforeEach goes what every test must have in a clean, known state: a new cart, a fresh user record. Mixing them is forbidden: put a mutable cart in beforeAll, and the first test that adds an item spoils the input of the second.
Cleanup must be symmetric to setup: what beforeAll opened, afterAll closes; what beforeEach set up, afterEach rolls back. Open a connection and you close it in afterAll, otherwise the Jest process hangs on the open handle. Fill a table in beforeEach and you clear it in afterEach, otherwise the rows of one test leak into the next. Symmetry is the guarantee: after any test the world returns to its original state, and run order stops affecting the result.
The order of hooks in nested describe blocks is strict. The outer beforeEach runs before the inner one, while afterEach runs in reverse: inner first, then outer. The outer block's beforeAll precedes the inner one's beforeAll, and afterAll is the other way around. For a test inside a nested block that is outer beforeEach, then inner, the test itself, inner afterEach, and finally outer afterEach.
Here is the classic trap. Jest first executes the bodies of all describe blocks in full - this is the collection phase, where it merely registers tests and hooks - and only then runs the tests themselves. So code in a describe body, rather than inside a hook or a test, runs before every beforeAll and every test, and for all blocks at once. Hence the bug: a value is computed right in the describe, on the assumption it is ready for the test, but it was computed during collection, before the beforeAll that was supposed to prepare the data. The place for such preparation is a hook, not the describe body.
From this comes the trade-off between shared setup and freshness. beforeEach gives independence: each test starts from a clean slate, but it pays a rebuild every time. beforeAll gives speed: the heavy resource is spun up once, but it requires that tests do not change it. The deciding signal is one: is the resource mutated by tests or not.
The failure comes when an expensive beforeAll holds mutable shared state. For speed the cart was created once in beforeAll. The test 'adds an item' puts a line in it and passes. The next, 'an empty cart shows zero', expects emptiness but sees the item from the previous one and fails - or, worse, passes by coincidence. Run alone the test is green, run inside the file it is red. The suite depends on order rather than behavior, and later this surfaces as flaky.
Mock isolation from the neighbouring chapter is a special case of the same symmetry for a different kind of shared state. There the subject is the reset policy - clearMocks/resetMocks and when to apply them; here it is the lifecycle primitives themselves, on which that policy rests. Mock resets are called from beforeEach or turned on by a config option that hangs the same hook. Master the four hooks and their order, and you clean any shared state - objects, mocks, resources - and the test again depends only on the code.
describe('checkout', () => {
let db: TestDb
let cart: Cart
beforeAll(async () => {
db = await connectTestDb() // expensive, spin up once
})
afterAll(async () => {
await db.close() // close the opened resource
})
beforeEach(() => {
cart = new Cart() // fresh cart for every test
})
afterEach(async () => {
await db.reset() // roll back the test's changes
})
test('adds an item to the cart', () => {
cart.add(product)
expect(cart.total).toBe(product.price)
})
})beforeAll(() => console.log('1 - beforeAll'))
afterAll(() => console.log('1 - afterAll'))
beforeEach(() => console.log('1 - beforeEach'))
afterEach(() => console.log('1 - afterEach'))
test('', () => console.log('1 - test'))
describe('nested', () => {
beforeAll(() => console.log('2 - beforeAll'))
afterAll(() => console.log('2 - afterAll'))
beforeEach(() => console.log('2 - beforeEach'))
afterEach(() => console.log('2 - afterEach'))
test('', () => console.log('2 - test'))
})
// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAlldescribe('outer', () => {
console.log('describe outer-a')
describe('inner 1', () => {
console.log('describe inner 1')
test('test 1', () => console.log('test 1'))
})
console.log('describe outer-b')
test('test 2', () => console.log('test 2'))
describe('inner 2', () => {
console.log('describe inner 2')
test('test 3', () => console.log('test 3'))
})
console.log('describe outer-c')
})
// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test 1
// test 2
// test 3