Jest Unit Testing: Extending the framework
Required Software
Hands down jest is the best javascript unit testing framework that I have had the pleasure of using. When they say delightful JavaScript Testing Framework
on their website they mean it!
Today we'll be walking through one of my favorite features of the jest
framework, the ability to extend it. There are three libraries
that we will be going over that can help simplify your tests and make writing them more enjoyable.
Getting Started
TIP
This post assumes that you have NodeJS
installed and npm
. Check that they are installed and working by running npm -v
and node -v
. Instructions for installing NodeJS
can be found at this post
To get started we need to run a few commands to set up our project.
# Make a folder for the project
mkdir extending-jest
# cd into that folder
cd extending-jest
# Create your package.json file in the directory
npm init -y
# Install jest
npm i -D jest
2
3
4
5
6
7
8
9
10
11
Next update your package.json
file in your project folder with a new test command, removing echo \"Error: no test specified\" && exit 1
.
{
"name": "extending-jest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.9.0"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Now that you've created the project and installed jest, we'll create a test that proves that the framework is set up and working.
// test.spec.js
test("20 + 22 should be the answer to life", () => {
expect(20 + 22).toBe(42)
})
2
3
4
5
Be sure to save the test.spec.js
file above and give your test a run by using the npm test
command within a terminal inside your project's directory. You should see the following printed to the terminal:
> jest
PASS ./test.spec.js
√ 20 + 22 should be the answer to life (2ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.311s
Ran all test suites.
2
3
4
5
6
7
8
9
10
Great we've got jest
setup! For the final step you need to add a jest
config section to your package.json
file, this section will be used later in the post to extend the framework.
TIP
You don't have to list your jest
config inside of your package.json
file, the same can be accomplished by creating a jest.config.js
file. By default jest
will look for either of the two without you having to specify them through the command-line interface.
// jest.config.js
module.exports = {}
2
// package.json
{
"name": "extending-jest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.9.0"
},
"jest": { }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Jest Extended
The first package, jest-extended, takes your expectations to the next level by adding additional matchers to your expect
calls. In the jest-extended
package there are matchers for Dates
, Booleans
, Mocks
, and so much more. If you would like to see a complete list of all of the additional matchers that the library supplies I would recommend checking out the jest-extended docs
Setup
To get started we will install the jest-extended
package as a dev dependency.
npm i -D jest-extended
Once the jest-extended
dependency has been installed you will need to add the package to the jest
config inside of the package.json
file. We do this by adding the setupFilesAfterEnv
property to the jest
config section and supplying an array with the string "jest-extended"
as an element of the array.
// package.json
{
...,
"jest": {
"setupFilesAfterEnv": ["jest-extended"]
}
}
2
3
4
5
6
7
Now the jest-extended
package is ready to be used in your tests!
Usage
Now that we have extended the framework, let's see what we can do! We'll start by creating a simple function for us to test inside of a spec file.
// extended.spec.js
const filterEvenNumbers = (items) => items.filter(i => i % 2 === 0)
2
Next, let's write our first test without the jest-extended
package to see the difference in value that the package can add to our tests once used.
Before
// extended.spec.js
...
describe("filterEvenNumbers", () => {
it("should filter array down to only even numbers", () => {
const numbers = filterEvenNumbers([1,2,3,4,5,6])
expect(numbers).toBeInstanceOf(Array)
expect(numbers).toHaveLength(3)
expect(numbers).toEqual(expect.arrayContaining([2, 4, 6]))
})
})
2
3
4
5
6
7
8
9
10
11
12
13
If you notice in the code above the goal of the first two assertions is to see if the variable numbers
is an Array
with a length of 3
. Using the library jest-extended
we can combine those two assertions into a single statement that checks both.
expect(numbers).toBeArrayOfSize(3)
The final assertion in this test checks that the returned array contains the values 2, 4, and 6
, the statement expect(numbers).toEqual(expect.arrayContaining([2, 4, 6]))
this statement does achieve this goal, but it could be a bit more cohesive. Luckily the jest-extended
packages provide us with a function that can do just that toIncludeAllMembers
, with which we can supply the same array.
expect(numbers).toIncludeAllMembers([2,4,6])
TIP
You could use a expect(numbers).toEquals([2,4,6])
instead and your tests would still pass the only problem is that if the order is not what you expect then your test will fail. For example expect(numbers).toEquals([4, 2, 6])
would cause a test failure whereas expect(numbers).toIncludeAllMembers([4, 2, 6])
will not since it only cares that the elements are present not that the elements are in a particular order.
The full conversion to jest-extended
matchers can be seen below:
After
// extended.spec.js
...
describe("filterEvenNumbers", () => {
it("should filter array down to only even numbers", () => {
const numbers = filterEvenNumbers([1,2,3,4,5,6])
expect(numbers).toBeArrayOfSize(3)
expect(numbers).toIncludeAllMembers([2, 4, 6])
})
})
2
3
4
5
6
7
8
9
10
TIP
I can't recommend enough going through the matchers available for the package on the jest-extended docs
Jest Chain
Next up we have jest-chain, this package allows us to chain our assertions together instead of having separate expect calls. This package essentially turns jest's
matchers into a Fluent Interface
allowing unlimited method chaining
.
TIP
If you would like to understand more about Fluent Interfaces
check out this article.
Setup
The setup for jest-chain
is the same as jest-extended
, first install the package and then add it to the setupFilesAfterEnv
array.
npm i -D jest-chain
// package.json
{
...,
"jest": {
"setupFilesAfterEnv": ["jest-extended", "jest-chain"]
}
}
2
3
4
5
6
7
Usage
Using the test that we created before we can see that instead of using the multiple assertions they can be all chained into a single expect statement:
Before
// extended.spec.js
...
describe("filterEvenNumbers", () => {
it("should filter array down to only even numbers", () => {
const numbers = filterEvenNumbers([1,2,3,4,5,6])
expect(numbers).toBeArrayOfSize(3)
expect(numbers).toIncludeAllMembers([2, 4, 6])
})
})
2
3
4
5
6
7
8
9
10
After
// extended.spec.js
...
describe("filterEvenNumbers", () => {
it("should filter array down to only even numbers", () => {
const numbers = filterEvenNumbers([1,2,3,4,5,6])
expect(numbers)
.toBeArrayOfSize(3)
.toIncludeAllMembers([2, 4, 6])
})
})
2
3
4
5
6
7
8
9
10
11
TIP
Since there is only one assertion, the code above could be simplified even further by calling the function inside of the expect function like so:
...
expect(filterEvenNumbers([1,2,3,4,5,6]))
...
2
3
As you can see even though jest-extended
is not a part of the base set of matcher that jest
provides it still works completely with the jest-chain
package. The best part about jest-chain
is that if you get errors for anything in the method chain the error output is still highly readable. You can check this by making either/both of the assertions false and check out the messages that you get.
Incorrect array size
● filterEvenNumbers › should filter array down to only even numbers
expect(received).toBeArrayOfSize(expected)
Expected value to be an array of size:
4
Received:
value: [2, 4, 6]
length: 3
6 |
7 | expect(numbers)
> 8 | .toBeArrayOfSize(4)
| ^
9 | .toIncludeAllMembers([2, 4, 6])
10 | })
11 |
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Unexpected member
● filterEvenNumbers › should filter array down to only even numbers
expect(received).toIncludeAllMembers(expected)
Expected list to have all of the following members:
[2, 4, 6, 8]
Received:
[2, 4, 6]
7 | expect(numbers)
8 | .toBeArrayOfSize(3)
> 9 | .toIncludeAllMembers([2, 4, 6, 8])
| ^
10 | })
11 |
12 | })
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Jest Expect Message
The final package jest-expect-message
is a simple package that allows you to add a custom message to your expectations, which can help with debugging test failures.
Setup
The jest-expect-message
is much like the other packages the only difference is that you will want to this package first in the setupFilesAfterEnv
array. This is because in the implementation of the package the definition of global expect
function is changed. Therefore you want the expect
function change to happen before the other packages attempt to make modifications to the global expect
function.
npm i -D jest-expect-message
{
...,
"jest": {
"setupFilesAfterEnv": [ "jest-expect-message", "jest-extended", "jest-chain" ]
}
}
2
3
4
5
6
WARNING
The order is important jest-expect-message
needs to be first in the list of set up packages.
Usage
Once again we will update our previous test to take advantage of the newly installed package. To use the jest-expect-message
package supply a message as the second argument of the expect
function, and if your assertions fail the custom message will be displayed.
To test that the message appears correctly we will add an element to the toIncludeAllMembers
assertion and force a failure.
// extended.spec.js
describe("filterEvenNumbers", () => {
it("should filter array down to only even numbers", () => {
const numbers = filterEvenNumbers([1,2,3,4,5,6])
expect(numbers, `Result of the filter: ${numbers}`)
.toBeArrayOfSize(3)
.toIncludeAllMembers([2, 4, 6, 8])
})
})
2
3
4
5
6
7
8
9
10
You should see the following custom error message:
● filterEvenNumbers › should filter array down to only even numbers
Custom message:
The array should only contain even numbers: 2,4,6
expect(received).toIncludeAllMembers(expected)
Expected list to have all of the following members:
[2, 4, 6, 8]
Received:
[2, 4, 6]
7 | expect(numbers, `The array should only contain even numbers: ${numbers}`)
8 | .toBeArrayOfSize(3)
> 9 | .toIncludeAllMembers([2, 4, 6, 8])
| ^
10 | })
11 |
12 | })
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
That's it for this tutorial on Extending Jest
great work making it all the way through! If you have any questions or feedback feel free to contact me or leave a comment below, and always remember you are the Captain of this Quality Cruise Line.
Resources