In our unit tests, we often have to write test code that runs before and after each test. We’ve to write them often to run code to set up fixtures before tests and run code to clean up everything after tests.
We can easily do this with Jest since it comes with a few hooks to do this.
In this article, we’ll look at how to write repetitive setup and teardown code in a way that isn’t repetitive.
How to Write Setup and Teardown Code
With Jest, we can write setup and teardown code by using the beforeEach
and afterEach
hooks.
beforeEach
runs code before each test, and afterEach
runs code after each test. The order applies inside a describe
block and if there’s no describe
block, for the whole file.
For example, given that we have the following code in example.js
:
const getStorage = (key) => localStorage.getItem(key);
const setStorage = (key, value) => localStorage.setItem(key, value);
module.exports = {
getStorage,
setStorage
}
We write the test for them as follows:
const { getStorage, setStorage } = require('./example');
beforeEach(() => {
localStorage.setItem('foo', 1);
expect(localStorage.getItem('bar')).toBeNull();
});
afterEach(() => {
localStorage.clear();
});
test('getStorage("foo") is 1', () => {
expect(getStorage('foo')).toBe('1');
});
test('setStorage saves data to local storage', () => {
setStorage('bar', 2);
const bar = +localStorage.getItem('bar');
expect(getStorage('foo')).toBe('1');
expect(bar).toBe(2);
});
We pass a callback function into the beforeEach
hook to run code before each test that we have below.
In this example, we prepopulate local storage with an item with key 'foo'
and value '1'
.
We also check that local storage doesn’t have the item with key 'bar’
with:
expect(localStorage.getItem('bar')).toBeNull();
All tests will pass with the expect
we have above since we’ll run localStorage.clear()
in the afterEach
hook.
Likewise, we pass in a callback to afterEach
to run code after each test is run.
We clear local storage with:
localStorage.clear();
Then when running the tests, we get that item with key'bar'
is null
since we didn’t populate it in the first test.
In the second test, we’ll get that expect(getStorage(‘foo’)).toBe(‘1’);
passing since we populated the local storage with it in our beforeEach
hook.
Then since we ran:
setStorage('bar', 2);
to to save an item with key 'bar'
and value '2'
, we’ll get that:
expect(bar).toBe(2);
passing since we saved the item in the test.
Asynchronous Code
In the example above, we ran synchronous code in our hooks. We can also run asynchronous code in our hooks if they either take a done
parameter or return a promise.
If we want to run a function that takes a callback and runs it asynchronously as follows:
const asyncFn = (callback) => {
setTimeout(callback, 500);
}
Then in the beforeEach
hook we can run asyncFn
as follows:
const { asyncFn } = require('./example');
beforeEach((done) => {
const callback = () => {
localStorage.setItem('foo', 1);
done();
}
asyncFn(callback);
expect(localStorage.getItem('bar')).toBeNull();
});
It does the same thing as the previous beforeEach
callback except it’s done asynchronously. Note that we call done
passed in from the parameter in our callback.
If we omit, it, the tests will fail as they time out. The code in the tests is the same as before.
We can wait for promises to resolve by returning it in the callback we pass into the hooks.
For example, in example.js
, we can write the following function to run a promise:
const promiseFn = () => {
return new Promise((resolve) => {
localStorage.setItem('foo', 1);
resolve();
});
}
Then we can put promiseFn
in module.exports
and then run it in our beforeEach
by running:
beforeEach(() => {
expect(localStorage.getItem('bar')).toBeNull();
return promiseFn();
});
We ran promiseFn
which we imported before this hook and we return the promise returned by that function, which sets the local storage like the first example except it’s done asynchronously.
Then after we put everything together, we have the following code in example.js
, which we run in our test code in the hooks and for testing:
const getStorage = (key) => localStorage.getItem(key);
const setStorage = (key, value) => localStorage.setItem(key, value);
const asyncFn = (callback) => {
setTimeout(callback, 500);
}
const promiseFn = () => {
return new Promise((resolve) => {
localStorage.setItem('foo', 1);
resolve();
});
}
module.exports = {
getStorage,
setStorage,
asyncFn,
promiseFn
}
Then we have asyncExample.test.js
to change the hook to be asynchronous:
const { getStorage, setStorage, asyncFn } = require('./example');
beforeEach((done) => {
const callback = () => {
localStorage.setItem('foo', 1);
done();
}
asyncFn(callback);
expect(localStorage.getItem('bar')).toBeNull();
});
afterEach(() => {
localStorage.clear();
});
test('getStorage("foo") is 1', () => {
expect(getStorage('foo')).toBe('1');
});
test('setStorage saves data to local storage', () => {
setStorage('bar', 2);
const bar = +localStorage.getItem('bar');
expect(getStorage('foo')).toBe('1');
expect(bar).toBe(2);
});
Then in example.test.js
we have:
const { getStorage, setStorage } = require('./example');
beforeEach(() => {
localStorage.setItem('foo', 1);
expect(localStorage.getItem('bar')).toBeNull();
});
afterEach(() => {
localStorage.clear();
});
test('getStorage("foo") is 1', () => {
expect(getStorage('foo')).toBe('1');
});
test('setStorage saves data to local storage', () => {
setStorage('bar', 2);
const bar = +localStorage.getItem('bar');
expect(getStorage('foo')).toBe('1');
expect(bar).toBe(2);
});
and finally in promiseExample.test.js
we have:
const { getStorage, setStorage, promiseFn } = require('./example');
beforeEach(() => {
expect(localStorage.getItem('bar')).toBeNull();
return promiseFn();
});
afterEach(() => {
localStorage.clear();
});
test('getStorage("foo") is 1', () => {
expect(getStorage('foo')).toBe('1');
});
test('setStorage saves data to local storage', () => {
setStorage('bar', 2);
const bar = +localStorage.getItem('bar');
expect(getStorage('foo')).toBe('1');
expect(bar).toBe(2);
});
BeforeAll and AfterAll
To only run the setup and teardown code once in each file, we can use the beforeAll
and afterAll
hooks. We can pass in a callback with the code that we want to run like we did with the beforeEach
and afterEach
hooks.
The only difference is that the callbacks are run once before the test in a file is run and after the test in a file is run instead of running them before and after each test.
Callback of beforeAll
runs after the callback for beforeEach
and callback for afterAll
runs after the callback forafterEach
.
The order applies inside a describe
block and if there’s no describe
block, for the whole file.
Running Only One Test
We can write test.only
instead of test
to run only one test. This is handy for troubleshooting since we don’t have to run all the tests and it helps us pin issues with the test that’s failing.
To write test code that needs to be run for all tests, we use the beforeEach
and afterEach
hooks in Jest.
To write test code that’s only run per describe
block or file, we can use the beforeAll
and afterAll
hooks.
Callback of beforeAll
runs after the callback for beforeEach
and callback for afterAll
runs after the callback forafterEach
.