How to Write Unit Tests with Jest
What is a Unit Test and Why Are They Important?
A unit test is a function that is written to test that another function is implemented properly. It should test various cases and verify that the function responds as expected. This will help you know if your code will run smoothly in production, whenever you make changes to your code, all unit tests should be run prior to deployment. This helps you catch potential bugs that may have been introduced while changes were made. To begin, let's consider a function that converts Fahrenheit to Celsius. We can start by creating a project.
Setup the project
Then follow the prompts (you can accept all the defaults)
This will add Jest as a development dependency because you typically don't need to include jest in your production build, it will just add unnecessary bloat. Also, make sure your scripts property in
Add our files
temperature.js will house our conversion function
temperature.spec.js will house our unit tests
Write our tests first
It is good practice to write your tests first. Some people like to name their test files with
.spec.js extension, suggesting that it is a specification for the code under test. Let's determine all the edge cases we want to test in order to make sure that our function will do what we want.
describe to group tests and
test to specify a single test. In this case we've also added
.todo to signify the test still needs to be written. For a list of globals, see the Jest API Docs.
Notice how we consider various cases such as invalid input, and certain expected temperature conversions. It is important to consider all the ways the function could potentially fail so you can write tests for each case.
Now if we run our tests with
npm test, you'll notice it returns that our test suite passed, and we have 7 todo tests.
Now, let's write our tests. First create a function in
temperature.js using the formula for temperature conversion.
We haven't handled all the cases, but now we can import the function into our test file to write our tests. Also, let's decide that we want to round to the nearest hundredth decimal place. Also we'll be using CommonJS modules because ECMAScript Modules aren't supported out of the box yet in Jest, but if you really want to use them, see ECMAScript Modules in Jest.
expect() is used to verify the output of our function, and the operator
.toBe() which uses
Object.is, is like a
=== check, only even more strict. For a list of operators see Jest Docs - Expect. Now if we run our tests, we get 2 passes and 5 failures.
We need to fix these failures so we can have all green! I like to run jest in watch mode so it will re-run all my tests automatically whenever I save my code.
npm test --watch
Here is the final working code:
How to Write a Good Unit Test?
Writing good tests starts by designing a good function. Unit testing forces you to define what you want the function to do before you write it. This encourages writing your code modularly so that it is easily testable and maintainable. Keeping functions concise and focused on a specific task is a great rule of thumb for writing testable code. It also tends to favor functional programming because it can be harder to write tests for code that is heavily object-oriented. This is because you have to account for state rather than just inputs and outputs.
There are different ways to write testable functions, but I've found it helpful to categorize functions into 2 categories:
- calculation functions
- orchestrator functions
Calculation functions take some input(s) and generate an output. They are very simple, they generally don't deal with state or call other functions. The temperature conversion function at the beginning is a good example of this type of function.
Orchestrator functions only call other functions, and sometimes return a result. In our unit tests for these functions, we only care about whether the functions inside were called, not what the result is. We already tested each individual function and those tests should suffice for testing calculations. This introduces function mocking, which means creating a fake version of a function just to test whether or not it was called.
In our example we could split our function into 2 functions. One that calculates the conversion, and another that rounds the result. Let's take a look:
and our tests
We can mock individual functions in the temperature module by using
jest.spyOn. This allows us to see if the function was called, how many times, and with what arguments. Then we call
.mockImplementation() to replace the mock function with our own. In this case we want
fahrenheitToCelsius to always return 4. This effectively isolates our tests on
convertDegrees from the other functions so that way if the other functions fail for some reason, we know whether
convertDegrees is still working or not. For a complete list of methods, see Jest Object.
To see the full final code, see the