Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Testing Fundamentals: 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:
code
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).
code
classUser{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 =newUser('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
beforeAll - per describe() scope (or global) - runs once before any test in that scope
beforeEach - runs before each test - parent beforeEach also runs for nested describe() blocks
afterEach - runs after each test - runs from inner to outer in nested describe() blocks
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:
code
beforeAll(()=>{});// runs before all testsdescribe('nested level 1',()=>{describe('nested level 2 - a',()=>{beforeEach(()=>{});// << runs before each test in this blocktest('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 testtest('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.