Introduction to Jest

Jest is one of the many JavaScript testing library, that had a lot of adoption because it’s developed by Facebook and is a perfect companion to React, another very popular Facebook Open Source project.

Jest didn’t just get popular because of this - software giants regularly ship projects that fail - but it got used by a lot of people because it’s great.

Among its key features we can highlight: - fast, parallel tests are distributed across workers to speed up execution - part of create-react-app, so straightforward to start with with no configuration needed - code coverage reports - support for React Native and TypeScript

Create the first Jest test

Projects created with create-react-app have Jest installed and preconfigured out of the box, but adding Jest to any project is as easy as typing

yarn add --dev jest

Add to your package.json this line:

{
  "scripts": {
    "test": "jest"
  }
}

and run your tests by executing yarn test in your shell.

Now, you don’t have any test here, so nothing is going to be executed:

Let’s create the first test. Open a math.js file and type a couple functions that we’ll later test:

const sum = (a, b) => a + b
const mul = (a, b) => a * b
const sub = (a, b) => a - b
const div = (a, b) => a / b

export default { sum, mul, sub, div }

Now create a math.test.js file, in the same folder, and there we’ll use Jest to test the functions defined in math.js:

const { sum, mul, sub, div } = require("./math")

test("Adding 1 + 1 equals 2", () => {
  expect(sum(1, 1)).toBe(2)
})
test("Multiplying 1 * 1 equals 1", () => {
  expect(mul(1, 1)).toBe(1)
})
test("Subtracting 1 - 1 equals 0", () => {
  expect(sub(1, 1)).toBe(0)
})
test("Dividing 1 / 1 equals 1", () => {
  expect(div(1, 1)).toBe(1)
})

Running yarn test results in Jest being run on all the test files it finds, and returning us the end result:

Matchers

In the previous article I used toBe() as the only matcher:

test("Adding 1 + 1 equals 2", () => {
  expect(sum(1, 1)).toBe(2)
})

A matcher is a method that lets you test values.

Most commonly used matchers, comparing the value of the result of expect() with the value passed in as argument, are: - toBe compares strict equality, using === - toEqual compares the values of two variables. If it’s an object or array, checks equality of all the properties or elements - toBeNull is true when passing a null value - toBeUndefined is true when passing an undefined value - toBeCloseTo is used to compare floating values, avoid rounding errors - toBeDefined is true when passing a defined value (opposite as above) - toBeTruthy true if the value is considered true (like an if does) - toBeFalsy true if the value is considered false (like an if does) - toBeGreaterThan true if the result of expect() is higher than the argument - toBeGreaterThanOrEqual true if the result of expect() is equal to the argument, or higher than the argument - toBeLessThan true if the result of expect() is lower than the argument - toBeLessThanOrEqual true if the result of expect() is equal to the argument, or lower than the argument - toMatch is used to compare strings with regular expression pattern matching - toContain is used in arrays, true if the expected array contains the argument in its elements set - toThrow checks if a function you pass throws an exception (in general) or a specific exception - toHaveBeenCalled checks if a function has been called at least once - toHaveBeenCalledTimes checks that a function has been called a specific number of times

All those matchers can be negated using .not. inside the statement, for example:

test("Adding 1 + 1 does not equal 3", () => {
  expect(sum(1, 1)).not.toBe(3)
})

Those are the most popular ones. See all the matchers you can use at https://facebook.github.io/jest/docs/en/expect.html

Mocks

In testing, mocking allows you to test functionality that depends on - Database - Network requests - access to Files - any External system

so that 1. your tests run faster, giving a quick turnaround time during development 2. your tests are independent of network conditions, the state of the database 3. your tests do not pollute any data storage because they do not touch the database 4. any change done in a test does not change the state for subsequent tests, and re-running the test suite should start from a known and reproduceable starting point 5. you don’t have to worry about rate limiting on API calls and network requests

Even more important, if you are writing a Unit Test, you should test the functionality of a function in isolation, not with all its baggage of things it touches.

You can do this in 2 ways: 1. mock functions 2. manual mocks