Jest’s setup and teardown hooks are like the pit crew for your tests, ensuring everything is in pristine condition before you start and cleaned up afterward. The most surprising thing about them is how much control you gain over test isolation and performance, often by simply changing one word.
Let’s see these hooks in action. Imagine we have a simple module that manages a list of users.
// userService.js
let users = [];
const addUser = (user) => {
users.push(user);
return user;
};
const getUsers = () => {
return users;
};
const clearUsers = () => {
users = [];
};
module.exports = { addUser, getUsers, clearUsers };
Now, let’s write some tests using Jest’s hooks.
// userService.test.js
const { addUser, getUsers, clearUsers } = require('./userService');
describe('User Service', () => {
// beforeAll(() => {
// console.log('--- Starting User Service Tests ---');
// });
// beforeEach(() => {
// console.log('--- Setting up a new test ---');
// clearUsers(); // Ensure a clean slate for each test
// });
// afterEach(() => {
// console.log('--- Tearing down a test ---');
// // In a real app, you might clean up database connections or mock servers here
// });
// afterAll(() => {
// console.log('--- All User Service Tests Finished ---');
// });
test('should add a user successfully', () => {
const newUser = { id: 1, name: 'Alice' };
const addedUser = addUser(newUser);
expect(addedUser).toEqual(newUser);
expect(getUsers()).toHaveLength(1);
});
test('should add another user successfully', () => {
const newUser = { id: 2, name: 'Bob' };
const addedUser = addUser(newUser);
expect(addedUser).toEqual(newUser);
expect(getUsers()).toHaveLength(1); // This is key for beforeEach
});
test('should get all users', () => {
addUser({ id: 3, name: 'Charlie' });
addUser({ id: 4, name: 'David' });
expect(getUsers()).toHaveLength(2);
});
});
If you uncomment the console.log statements and run npm test, you’ll see the order of execution. beforeAll runs once before any tests in the describe block. beforeEach runs before each individual test. afterEach runs after each individual test. afterAll runs once after all tests in the describe block have completed.
The core problem these hooks solve is test isolation and managing shared resources. Without beforeEach(() => clearUsers()), the second test (should add another user successfully) would fail because the users array would already contain 'Alice' from the first test, and getUsers() would return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }], making the assertion toHaveLength(1) false. beforeEach guarantees that each test starts with a fresh, predictable state.
beforeAll and afterAll are for setup and teardown that you only need to do once for the entire suite of tests within a describe block. Think of setting up a test database connection or starting a mock server before any tests run, and then tearing it down only after all tests are finished. This is crucial for performance; you don’t want to establish a database connection for every single test if it’s not necessary.
The difference between beforeEach and beforeAll is when they execute. beforeAll runs once at the beginning of the describe block, while beforeEach runs before every single test inside that describe block. Similarly, afterEach runs after each test, and afterAll runs once at the very end.
The subtle power of beforeEach is that it can be used to reset global state or mock objects that are modified by tests. In our userService.js example, clearUsers() is called in beforeEach to ensure that each test operates on an empty user list, making tests independent of each other. If you were testing a more complex system, beforeEach might involve mocking API calls, resetting in-memory caches, or creating temporary files that each test needs to start from scratch.
When you have multiple describe blocks, hooks are scoped to their respective describe blocks. A beforeAll in an outer describe will run before beforeAll in an inner describe, but beforeEach inside the inner describe will run for tests in the inner describe only.
The most common mistake is to confuse beforeAll with beforeEach. If you need a clean slate for every test, use beforeEach. If you only need to set something up once for efficiency, and that setup doesn’t need to be reset between tests, use beforeAll.
The next logical step is to understand how to use these hooks with asynchronous operations, especially when dealing with promises or async/await.