JavaScript and TypeScript course Β· Module 11: Testing with Jest
Setup and Teardown
In this lesson5
Imagine three tests that share one enclosure. The first lets a T-Rex in, the second checks whether the enclosure is empty - and fails, because the T-Rex is still there. Tests that share state start depending on each other: the result depends on the order in which they run, and a failure in one knocks over the next ones like dominoes.
In Jurassic Park, before every enclosure inspection you need to prepare equipment, and after the inspection you need to clean up. It is exactly the same with tests - setup and teardown functions allow you to prepare and clean up the test environment. In Jest they are called hooks, and there are four of them: beforeEach, afterEach, beforeAll and afterAll.
beforeEach - Before Every Test
beforeEach runs before each test in a describe block. It is the ideal place for creating fresh test data. You declare the variables with let in the describe scope and assign their values in the hook. You know the createEnclosure and createDinosaur factories from the previous lessons:
1describe('DinosaurEnclosure', () => {
2 let enclosure;
3 let rex;
4
5 beforeEach(() => {
6 // Before each test we create a new enclosure and dinosaur
7 enclosure = createEnclosure('Zone A', 5);
8 rex = createDinosaur('Rex', 'T-Rex', 'carnivore');
9 });
10
11 it('should add dinosaur to enclosure', () => {
12 enclosure.add(rex);
13 expect(enclosure.dinosaurs).toHaveLength(1);
14 expect(enclosure.dinosaurs[0].name).toBe('Rex');
15 });
16
17 it('should start with empty enclosure', () => {
18 // Thanks to beforeEach, enclosure is always fresh!
19 expect(enclosure.dinosaurs).toHaveLength(0);
20 });
21
22 it('should track capacity', () => {
23 enclosure.add(rex);
24 expect(enclosure.remainingCapacity()).toBe(4);
25 });
26});The second test passes even though the first one added Rex to the enclosure. The old object did not magically disappear - beforeEach simply assigned a brand-new enclosure to the enclosure variable. This is the Arrange section of the AAA pattern, moved into a hook so you do not repeat it in every test.
afterEach - After Every Test
afterEach runs after each test. It serves to clean up after tests - closing connections, resetting state. In the example, SecuritySystem is the security system class: onAlarm registers a function called on an alarm, simulateBreach simulates a fence breach, and deactivate switches the system off:
1describe('SecuritySystem', () => {
2 let securitySystem;
3 let alarmLog;
4
5 beforeEach(() => {
6 securitySystem = new SecuritySystem();
7 alarmLog = [];
8 securitySystem.onAlarm((msg) => alarmLog.push(msg));
9 });
10
11 afterEach(() => {
12 // Reset the security system after each test
13 securitySystem.deactivate();
14 alarmLog = [];
15 console.log('System reset after test');
16 });
17
18 it('should detect breach', () => {
19 securitySystem.simulateBreach('Zone A');
20 expect(alarmLog).toContain('Breach in Zone A');
21 });
22
23 it('should handle multiple alarms', () => {
24 securitySystem.simulateBreach('Zone A');
25 securitySystem.simulateBreach('Zone B');
26 expect(alarmLog).toHaveLength(2);
27 });
28});afterEach runs even when a test fails, which makes it the best place for the cleanup stage. The line with console.log only shows the moment of execution - you do not need it in real tests.
beforeAll and afterAll - Once for the Entire Group
beforeAll runs once before all tests in a describe, and afterAll once after all. Use them for expensive operations. connectToDatabase is an example asynchronous function that connects to a database. Hooks, just like tests, can be async, and Jest will wait for them to finish:
1describe('DatabaseTests', () => {
2 let database;
3
4 beforeAll(async () => {
5 // Expensive operation - database connection, only once!
6 database = await connectToDatabase();
7 console.log('Database connected');
8 });
9
10 afterAll(async () => {
11 // Close connection after all tests
12 await database.close();
13 console.log('Database disconnected');
14 });
15
16 beforeEach(async () => {
17 // Clear data before each test
18 await database.clear();
19 });
20
21 it('should save dinosaur', async () => {
22 await database.save({ name: 'Rex', species: 'T-Rex' });
23 const dinos = await database.findAll();
24 expect(dinos).toHaveLength(1);
25 });
26
27 it('should find by species', async () => {
28 await database.save({ name: 'Rex', species: 'T-Rex' });
29 await database.save({ name: 'Blue', species: 'Velociraptor' });
30
31 const raptors = await database.findBySpecies('Velociraptor');
32 expect(raptors).toHaveLength(1);
33 expect(raptors[0].name).toBe('Blue');
34 });
35});The connection is created only once, but the data is cleared before every test in beforeEach. It is a typical duo: an expensive shared resource and cheap state that gets refreshed. My advice: create everything that can be rebuilt quickly in beforeEach, and keep beforeAll for truly expensive operations. Shared state is the most common source of tests that pass only in a particular order.
How do you spot such a leak? A test passes when you run it on its own but fails as part of the whole suite. The Jest documentation suggests temporarily changing test to test.only (or it to it.only) to run just that one case, and then looking for state that another test modifies.
Execution Order
It is important to understand the order in which Jest executes setup and teardown. The block below prints the step number from every hook and every test to the console:
1describe('Execution order', () => {
2 beforeAll(() => console.log('1. beforeAll'));
3 afterAll(() => console.log('5. afterAll'));
4 beforeEach(() => console.log('2. beforeEach'));
5 afterEach(() => console.log('4. afterEach'));
6
7 it('test 1', () => console.log('3. test 1'));
8 it('test 2', () => console.log('3. test 2'));
9});
10
11// Output:
12// 1. beforeAll
13// 2. beforeEach
14// 3. test 1
15// 4. afterEach
16// 2. beforeEach
17// 3. test 2
18// 4. afterEach
19// 5. afterAllbeforeAll and afterAll appear in the output only once, while the beforeEach and afterEach pair wraps each test separately. Jest has one more trait worth knowing: it first executes all describe functions in a file, collecting the list of tests, and only then runs the tests themselves. Code written directly in a describe, outside a hook, therefore runs earlier than you might expect.
Nested describe
Setup and teardown work hierarchically in nested describe blocks. It resembles the park's zones, which have their own procedures but also follow the rules of the whole park:
1describe('Jurassic Park', () => {
2 beforeEach(() => console.log('Setup: Park'));
3
4 describe('Predator Zone', () => {
5 beforeEach(() => console.log('Setup: Predator Zone'));
6
7 it('T-Rex is in the enclosure', () => {
8 console.log('Test: T-Rex');
9 // Setup order: Park -> Predator Zone
10 });
11 });
12
13 describe('Herbivore Zone', () => {
14 beforeEach(() => console.log('Setup: Herbivore Zone'));
15
16 it('Triceratops is in the enclosure', () => {
17 console.log('Test: Triceratops');
18 // Setup order: Park -> Herbivore Zone
19 });
20 });
21});Nested describe blocks inherit setup from parent blocks. beforeEach from the parent runs before the child's beforeEach. During cleanup the order is reversed: first the child's afterEach, then the parent's. Hooks declared at the top level of a file, outside any describe, apply to every test in that file.
In the lesson on mocking you will use beforeEach to clear fake functions, and with asynchronous code to switch on fake timers. In the lab below you will see the order of hooks printed step by step.
Remember: beforeEach is the morning inspection of the enclosure - every test starts its day on clean, checked ground.
Code for this lesson: index.js
1// Setup and Teardown - beforeEach, afterEach, beforeAll, afterAll
2console.log("=== Jurassic Park - Enclosure Inspection ===\n");
3
4// Setup/teardown simulation
5const log = [];
6let currentDescribe = "";
7
8function logStep(step) {
9 log.push(step);
10 console.log(` ${step}`);
11}
12
13// --- Order demonstration ---
14console.log("--- Execution order ---\n");
15
16// Simulation
17const steps = [];
18
19function simulateTestRun() {
20 // beforeAll
21 steps.push("1. beforeAll - connect to the database");
22
23 // Test 1
24 steps.push("2. beforeEach - set up the environment");
25 steps.push("3. TEST 1 - check the enclosure");
26 steps.push("4. afterEach - clean up");
27
28 // Test 2
29 steps.push("5. beforeEach - set up the environment");
30 steps.push("6. TEST 2 - check the sensor");
31 steps.push("7. afterEach - clean up");
32
33 // afterAll
34 steps.push("8. afterAll - close the database");
35}
36
37simulateTestRun();
38steps.forEach(s => console.log(s));
39
40// --- Practical example ---
41console.log("\n--- Practical example: beforeEach ---\n");
42
43function createEnclosure(name, capacity) {
44 return {
45 name,
46 capacity,
47 dinosaurs: [],
48 add(dino) { this.dinosaurs.push(dino); },
49 remainingCapacity() { return this.capacity - this.dinosaurs.length; }
50 };
51}
52
53// Simulating tests with beforeEach
54let enclosure;
55let rex;
56
57function beforeEach() {
58 enclosure = createEnclosure("Zone A", 5);
59 rex = { name: "Rex", species: "T-Rex" };
60 console.log(" [setup] New enclosure and dinosaur created");
61}
62
63// Test 1
64beforeEach();
65enclosure.add(rex);
66console.log(` [test1] Dinosaurs: ${enclosure.dinosaurs.length} -> ${enclosure.dinosaurs.length === 1 ? "PASS" : "FAIL"}`);
67
68// Test 2 - the enclosure is fresh thanks to beforeEach!
69beforeEach();
70console.log(` [test2] Empty enclosure: ${enclosure.dinosaurs.length} -> ${enclosure.dinosaurs.length === 0 ? "PASS" : "FAIL"}`);
71
72// Test 3
73beforeEach();
74enclosure.add(rex);
75console.log(` [test3] Remaining: ${enclosure.remainingCapacity()} -> ${enclosure.remainingCapacity() === 4 ? "PASS" : "FAIL"}`);
76
77// --- Nested describe ---
78console.log("\n--- Nested describe ---\n");
79
80function simulateNested() {
81 const order = [];
82
83 // Outer beforeEach
84 order.push("Park: beforeEach");
85
86 // Inner: Predator Zone
87 order.push(" Predator Zone: beforeEach");
88 order.push(" TEST: T-Rex is in the enclosure");
89 order.push(" Predator Zone: afterEach");
90 order.push("Park: afterEach");
91
92 order.push(""); // separator
93
94 order.push("Park: beforeEach");
95 order.push(" Herbivore Zone: beforeEach");
96 order.push(" TEST: Triceratops is in the enclosure");
97 order.push(" Herbivore Zone: afterEach");
98 order.push("Park: afterEach");
99
100 return order;
101}
102
103simulateNested().forEach(s => console.log(s));
104
105// --- beforeAll vs beforeEach ---
106console.log("\n--- beforeAll vs beforeEach ---\n");
107console.log("beforeAll: once before ALL tests (e.g. connecting to the database)");
108console.log("beforeEach: before EACH test (e.g. clearing data)");
109console.log("afterEach: after EACH test (e.g. resetting state)");
110console.log("afterAll: once after ALL tests (e.g. closing the connection)");Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. When does the beforeEach function execute in Jest?
2. What is the difference between beforeAll and beforeEach?
Hands-on tasks in the game
- Code editor
Write tests for a fence system with beforeEach ensuring a clean state.
- Vertical ordering
Arrange the execution order of setup/teardown functions in Jest:
- Click in order
Arrange the elements of a beforeEach declaration:
- Code editor
Create tests with nested describe blocks and separate beforeEach hooks.