Testing JavaScript Modules with Jest
Jest is a very easy to use testing library for JavaScript code, that includes handy features such as generating code-coverage and reports and watching for changes to your source code to automatically re-run tests.
It is incredibly easy to get started with Jest; in our example, we are going to get started in under 15 minutes by building out tests for a very simple calculator module.
First off, let's assume the following directory structure:
-my-calculator
--calculator.js
--package.json
Where package.json
has the contents of:
{
"name": "my-calculator",
"version": "1.0.0",
"description": "A calculator for practicing Jest tests",
"main": "calculator.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}
And where calculator.js
has the contents of:
const add = (x, y) => {
return x + y;
};
const subtract = (x, y) => {
return x - y;
};
const multiply = (x, y) => {
return x * y;
};
const divide = (x, y) => {
return x / y;
};
module.exports = {
add,
subtract,
multiply,
divide
};
This is a very simple calculator, with tons of room for improvement. For now, we're going to focus on writing some very, very basic unit tests for our calculator.
At this point, we're going to add the node module for Jest using npm (or yarn, if you prefer), saving it as a dev-dependency
:
> npm install --save-dev jest
or
> yarn add --dev jest
We are also going to update our package.json
file to run jest
as our test script, making package.json:
{
"name": "my-calculator",
"version": "1.0.0",
"description": "A calculator for practicing Jest tests",
"main": "calculator.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "MIT",
"devDependencies": {
"jest": "^22.1.3"
}
}
This will allow us to run the npm test
command; for now, this will error out because we do not have any testing files.
To allow Jest to run, we must also also add a file called calculator.test.js
alongside calculator.js
, giving us the folder structure of:
-my-calculator
--node_modules/
--calculator.js
--calculator.test.js
--package.json
Our calculator.test.js
will import our calculator
file, and perform tests on it to make sure that our module is well made. We will organize our Jest tests into to sections denoted with the describe
method.
In our calculator.test.js
file, we will first include our module and then test our some basic addition and subtraction
const { add, subtract, multiply, divide } = require("./calculator");
describe("valid additions", () => {
test("1 + 1 = 2", () => {
expect(add(1, 1)).toEqual(2);
});
test("10 + 20 = 30", () => {
expect(add(10, 20)).toEqual(30);
});
});
describe("valid subtractions", () => {
test("10 - 2 = 8", () => {
expect(subtract(10, 2)).toEqual(8);
});
test("87 - 523 = -436", () => {
expect(subtract(87, 523)).toEqual(-436);
});
});
Now, if we run npm test
, Jest will run and give us a test report:
~/Work/my-calculator > npm test
> my-calculator@1.0.0 test /Users/xueye/Work/my-calculator
> jest
PASS ./calculator.test.js
valid additions
✓ 1 + 1 = 2 (3ms)
✓ 10 + 20 = 30 (1ms)
valid subtractions
✓ 10 - 2 = 8
✓ 87 - 523 = -436
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 0.703s, estimated 1s
Ran all test suites.
These tests are great, but there's a lot more information we need!
Let's go back to our package.json
file and update the test
command to be jest --coverage
, and run it again:
~/Work/my-calculator > npm test
> my-calculator@1.0.0 test /Users/xueye/Work/my-calculator
> jest --coverage
PASS ./calculator.test.js
valid additions
✓ 1 + 1 = 2 (4ms)
✓ 10 + 20 = 30 (1ms)
valid subtractions
✓ 10 - 2 = 8
✓ 87 - 523 = -436 (1ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 1.038s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 77.78 | 100 | 50 | 77.78 | |
calculator.js | 77.78 | 100 | 50 | 77.78 | 10,14 |
---------------|----------|----------|----------|----------|----------------|
Now, we get a coverage report! We're doing fairly okay, but we've got some room to improve. We've got to test multiply
and divide
. Let's add some more tests to calculator.test.js
:
const { add, subtract, multiply, divide } = require("./calculator");
describe("valid additions", () => {
test("1 + 1 = 2", () => {
expect(add(1, 1)).toEqual(2);
});
test("10 + 20 = 30", () => {
expect(add(10, 20)).toEqual(30);
});
});
describe("valid subtractions", () => {
test("10 - 2 = 8", () => {
expect(subtract(10, 2)).toEqual(8);
});
test("87 - 523 = -436", () => {
expect(subtract(87, 523)).toEqual(-436);
});
});
describe("valid multiplications", () => {
test("2 * 4 = 8", () => {
expect(multiply(2, 4)).toEqual(8);
});
test("1000 * 8.5 = 8500", () => {
expect(multiply(1000, 8.5)).toEqual(8500);
});
});
describe("valid divisions", () => {
test("20 / 2 = 10", () => {
expect(divide(20, 2)).toEqual(10);
});
test("99 / 9 = 11", () => {
expect(divide(99, 9)).toEqual(11);
});
});
And if we run npm test
we now get:
~/Work/my-calculator > npm test
> my-calculator@1.0.0 test /Users/xueye/Work/my-calculator
> jest --coverage
PASS ./calculator.test.js
valid additions
✓ 1 + 1 = 2 (3ms)
✓ 10 + 20 = 30
valid subtractions
✓ 10 - 2 = 8 (1ms)
✓ 87 - 523 = -436
valid multiplications
✓ 2 * 4 = 8
✓ 1000 * 8.5 = 8500 (1ms)
valid divisions
✓ 20 / 2 = 10
✓ 99 / 9 = 11
Test Suites: 1 passed, 1 total
Tests: 8 passed, 8 total
Snapshots: 0 total
Time: 0.737s, estimated 1s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
calculator.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Much better! Now we have 100% coverage! However, we haven't actually tested much -- so let's update our division
function to throw an exception if we try to divide by 0:
const divide = (x, y) => {
if (y === 0) {
throw "Cannot divide by 0!";
}
return x / y;
};
And let's add a new test, to ensure that dividing by 0 throws in calculator.test.js
:
describe("divison error cases", () => {
test("20 / 0 throws", () => {
expect(() => {
expect(divide(20, 0));
}).toThrow();
});
});
Running npm test
now gives us:
~/Work/my-calculator > npm test
> my-calculator@1.0.0 test /Users/xueye/Work/my-calculator
> jest --coverage
PASS ./calculator.test.js
valid additions
✓ 1 + 1 = 2 (3ms)
✓ 10 + 20 = 30
valid subtractions
✓ 10 - 2 = 8
✓ 87 - 523 = -436
valid multiplications
✓ 2 * 4 = 8
✓ 1000 * 8.5 = 8500 (1ms)
valid divisions
✓ 20 / 2 = 10
✓ 99 / 9 = 11
divison error cases
✓ 20 / 0 throws (1ms)
Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total
Snapshots: 0 total
Time: 0.988s, estimated 1s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
calculator.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Now we know that we cannot divide by 0, making our module more predictable. We also know that our method does in fact throw.
As a general rule, I like to at least add the following test cases for each method:
- Check valid inputs
- Check that it throws when no inputs are given
- Check that it throws when inputs are not of the proper type
- Check that it throws when given illogical input
- Check that any edge cases are handled properly.
Want to see more advanced tests? Check out my tests for my triever and fluent-sort!