Setup and Teardown with beforeEach/afterEach

When testing real applications you will often have to 'set up the world' before all or before each test.

Examples include setting up a database connection, clearing or resetting mocks, and resetting window.localStorage mock data.

If a test file has multiple test() or it() (which it probably will), you won't want to copy/paste and put these in each test. It gets harder to read and harder to update.

So it is very common to see use of these (often known as setup and teardown functions)

  • beforeAll()
  • beforeEach()
  • afterEach()
  • afterAll()

One of the most common things you will see is something like the following to clear all mock call history, so tests don't leak into other tests:

beforeEach(() => {
  jest.clearAllMocks(); // or vi.clearAllMocks()
});

In this guide we'll go over how to use them, and how nested describe()/test() blocks use them.

Simple example

In the following example we want to run tests on the User class.

We could instantiate const user = new User() in every test (and for such a simple example it might be cleaner).

But in real apps there can be a lot of setup code before each test.

So in this example before each test we create a fresh User instance (in beforeEach()).

Without this, the isActive or email properties will leak from a previous test into the next test.

We are using beforeEach() which runs before every test() or it().

There is also beforeAll(), which runs once per describe scope (or globally) before any test() or it() in that scope runs (see notes on nesting below).

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
    this.isActive = true;
  }

  deactivate() {
    this.isActive = false;
  }

  updateEmail(newEmail) {
    this.email = newEmail;
  }
}

describe('User class tests', () => {
  let user;

  beforeEach(() => {
    user = new User(
      'Bob',
      'bob@example.com'
    );
  });

  test('should create user with correct initial values', () => {
    expect(user.name).toBe('Bob');
    expect(user.email).toBe(
      'bob@example.com'
    );
    expect(user.isActive).toBe(true);
  });

  test('should deactivate user account', () => {
    user.deactivate();
    expect(user.isActive).toBe(false);
  });

  test('should update user email', () => {
    user.updateEmail(
      'newemail@example.com'
    );
    expect(user.email).toBe(
      'newemail@example.com'
    );
  });
});

Order of calls

  1. beforeAll - per describe() scope (or global) - runs once before any test in that scope
  2. beforeEach - runs before each test - parent beforeEach also runs for nested describe() blocks
  3. afterEach - runs after each test - runs from inner to outer in nested describe() blocks
  4. afterAll - per describe() scope (or global) - runs once after all tests in that scope

Nested describe blocks

We covered describe() blocks in a previous lesson, to help organise your tests.

They are also used to control the following:

  • A beforeAll/afterAll within a describe() block will run before/after running tests within that describe block
  • A beforeEach/afterEach within a describe() block will only run before/after tests within that describe block

Here is an example:

beforeAll(() => {}); // runs before all tests

describe('nested level 1', () => {
  describe('nested level 2 - a', () => {
    beforeEach(() => {}); // << runs before each test in this block

    test('some test', () => {});
    test('another test', () => {});

    describe('nested level 3', () => {
      test('a third test', () => {}); // << the beforeEach will run before this too, as it is still within that same describe block
    });
  });

  describe('another describe block', () => {
    // there is no `beforeEach` that will run before this test
    test('a test', () => {});
  });
});

Lesson Task

Add a beforeEach(() => {...}) which sets calculator = new Calculator, so that when the test runs there is a new instance of the Calculator class for the test to use.

Ready to try running tests for this lesson?