Jest is a testing framework open-source by Facebook that integrates assertion libraries, mocks, snapshot testing, coverage reporting, and more. It’s great for testing React code, but not only that, all JS code can be tested using Jest.

This article provides a comprehensive overview of how to use Jest, making it easy for newcomers to get started. In this article, I will select the key parts and post the codes directly, but not the simple parts. The main reason is that when I write the later part, I find the posted codes are a little too much and not interesting. All the codes have been uploaded to Github and can be checked by myself.

The installation

Install Jest using YARN:

$ yarn add --dev jest
Copy the code

Or use NPM:

$ npm i -D jest
Copy the code

The –dev and -d arguments are specified as devDependencies, so that the dependency is installed only in the development environment and not in the build environment.

Add the following to the package.json file:

"scripts": {
  "test": "jest"
}
Copy the code

This allows us to execute the test code through YARN test or NPM test.

Similarly, you can also choose to install Jest globally:

$ yarn global add jest
$ # or npm i -g jest
Copy the code

This way you can use jest directly at the command line. If you have a local installation but also want to use jest on the command line, you can access the bin version of it via node_modules/.bin/webpack, or NPX jest if your NPM version is 5.2.0 or older.

The use of Babel

If you use a new syntax feature in your code that the current Node version does not support, you will need to use Babel to escape it.

$ npm i -D babel-jest babel-core babel-preset-env
Copy the code

NPM I -d babel-jest ‘babel-core@^7.0.0-0’ @babel/core

Jest defaults to using babel-Jest (install required) for code escape. If you need to add additional preprocessors, you need to define babel-Jest as a JavaScript processor as shown in the Jest configuration file (because once you add the Transform configuration, Babel-jest will not load automatically) :

"transform": { "^.+\\.jsx? $": "babel-jest" },Copy the code

We also need to create the.babelrc file in the root directory:

{
  "presets": [
    "env"]}Copy the code

I only use the babel-preset-env preset here, see Babel for additional conversions.

Basic usage

We’ll start with a basic Math module. First create a math.js file:

// basic/math.js

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 { sum, mul, sub, div }
Copy the code

To test whether the Math module is correct, we need to write test code. Typically, the test file has the same name as the source file being tested, but with a suffix of.test.js or.spec.js. Here we create a math.test.js file:

// basic/math.test.js

import { sum, mul, sub, div } from './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)})Copy the code

Executing NPM test Jest will execute all matching test files and return test results:

Run it in the editor

Many editors support Jest, such as Webstorm, VS Code, Atom, etc. Here is a brief description of how to run it in Webstorm and VS Code.

Webstorm

Webstorm cannot find the problems, such as variable may occur in the Preferences | Languages and Frameworks JavaScript | | click Download in Libraries, and then select the Jest and Download it.

Webstorm can recognize the test code and run it by clicking “corresponding Run button” in the editor, or by using the shortcut CTRL + Shift +R (on MAC). See my previous node.js blog post on unit testing with Mocha for details.

VS Code

To run in VS Code, we need to install the Jest plug-in.

Once the plug-in is installed, if you have Jest installed, it will automatically run the test code. You can do this manually by running the Jest: Start Runner command, which executes the test code and rerunks it if the file changes.

matcher

Matchers are used to implement the assertion function. In the previous example, we only used the toBe() matcher:

test('Adding 1 + 1 equals 2', () => {
  expect(sum(1, 1)).toBe(2)
})
Copy the code

In this code, expect(sum(1, 1)) returns an “expected” object, and.tobe (2) is the matcher. The matcher compares Expect () ‘s result (the actual value) with its own parameter (the expected value). When Jest runs, it keeps track of all failed matchers and prints an error message.

Common matchers are as follows:

  • toBeuseObject.isDetermine if it is strictly equal.
  • toEqualRecursively examine each field of an object or array.
  • toBeNullMatches onlynull.
  • toBeUndefinedMatches onlyundefined.
  • toBeDefinedMatches only theundefined.
  • toBeTruthyOnly matches true.
  • toBeFalsyOnly matches false.
  • toBeGreaterThanThe actual value is greater than expected.
  • toBeGreaterThanOrEqualThe actual value is greater than or equal to the expected value
  • toBeLessThanThe actual value is less than the expected value.
  • toBeLessThanOrEqualThe actual value is less than or equal to the expected value.
  • toBeCloseToCompares floating-point values to avoid errors.
  • toMatchRegular matching.
  • toContainChecks whether the array contains the specified item.
  • .toHaveProperty(keyPath, value)Determines whether the object contains the specified attribute.
  • toThrowDetermines whether the specified exception is thrown.
  • toBeInstanceOfDetermines whether an object is an instance of a class, used by the underlyinginstanceof.

All matchers can use the.not inverse:

test('Adding 1 + 1 does not equal 3', () => {
  expect(1 + 1).not.toBe(3)})Copy the code

For Promise objects, we can use.convergent and.rejects:

// .resolves
test('resolves to lemon', () = > {// make sure to add a return statement
  return expect(Promise.resolve('lemon')).resolves.toBe('lemon')})// .rejects
test('rejects to octopus', () = > {// make sure to add a return statement
  return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
    'octopus',)})Copy the code

Asynchronous test

JavaScript code often contains asynchronous code, and when testing asynchronous code, Jest needs to know when the asynchronous code is finished, so it can execute other tests before the asynchronous code is finished. Jest provides several ways to test asynchronous code.

The callback function

Jest considers the test complete when it reaches the end of the test code. Therefore, Jest does not wait for the callback function to execute if there is asynchronous code. To fix this, in the test function we take an argument called done, and Jest will wait until we call done(). If done() is never called, this test fails.

// async/fetch.js
export const fetchApple = (callback) = > {
  setTimeout((a)= > callback('apple'), 300)}// async/fetch.test.js
import { fetchApple } from './fetch'

test('the data is apple', (done) => {
  expect.assertions(1)
  const callback = data= > {
    expect(data).toBe('apple')
    done()
  }

  fetchApple(callback)
})
Copy the code

Expect. Assertions (1) Verifies that one assertion will be executed in the current test, ensuring that assertions in the callback are executed when testing asynchronous code.

Promise

If the asynchronous code returns the Promise object, Jest will wait for the Resolved object and rejected will fail the test.

test('the data is banana', () => {
  expect.assertions(1)
  return fetchBanana().then(data => expect(data).toBe('banana'))})Copy the code

If you expect the promise to be in the Rejected state, you can use.catch() :

test('the fetch fails with an error', () => {
  expect.assertions(1)
  return fetchError().catch(e= > expect(e).toMatch('error'))})Copy the code

In addition to this, the.convergency and.rejects can be used as mentioned above.

Async/Await

If asynchronous code returns a promise, we can also use async/await:

test('async: the data is banana'.async () => {
  expect.assertions(1)
  const data = await fetchBanana()
  expect(data).toBe('banana')
})

test('async: the fetch fails with an error'.async () => {
  expect.assertions(1)
  try {
    await fetchError()
  } catch (e) {
    expect(e).toMatch('error')}})Copy the code

It is also possible to combine aysnc/awiat with.convergency or.rejects:

test('combine async with `.resolves`'.async () => {
  expect.assertions(1)
  await expect(fetchBanana()).resolves.toBe('banana')})Copy the code

Hook function

Jest gives us hooks for four test cases: beforeAll(), afterAll(), beforeEach(), afterEach().

BeforeAll () and afterAll() are executed once before and afterAll test cases. BeforeEach () and afterEach() are executed before and afterEach test case.

grouping

We can use Describe to group test cases, and the hook functions in the Describe block only apply to test cases within the block:

beforeAll((a)= > console.log('1 - beforeAll')) / / 1
afterAll((a)= > console.log('1 - afterAll')) / / 12
beforeEach((a)= > console.log('1 - beforeEach')) / / 2, 6
afterEach((a)= > console.log('1 - afterEach')) / / 4, 10
test(' ', () = >console.log('1 - test')) / / 3
describe('Scoped / Nested block', () => {
  beforeAll((a)= > console.log('2 - beforeAll')) / / 5
  afterAll((a)= > console.log('2 - afterAll')) / / 11
  beforeEach((a)= > console.log('2 - beforeEach')) / / 7
  afterEach((a)= > console.log('2 - afterEach')) / / 9
  test(' ', () = >console.log('2 - test')) / / 8
})
Copy the code

Note that the top-level beforeEach is executed before beforeEach within the Describe block.

Jest executes operations in the Describe block first, and when the operations in the Describe block are complete, the test cases are executed in the order that they appear in Describe, so initialization and destruction should be run in hook functions, not in the Describe block:

describe('outer', () = > {console.log('describe outer-a') / / 1

  describe('describe inner 1', () = > {console.log('describe inner 1') / / 2
    test('test 1', () = > {console.log('test for describe inner 1') / / 6
      expect(true).toEqual(true)})})console.log('describe outer-b') / / 3

  test('test 1', () = > {console.log('test for describe outer') / / 7
    expect(true).toEqual(true)
  })

  describe('describe inner 2', () = > {console.log('describe inner 2') / / 4
    test('test for describe inner 2', () = > {console.log('test for describe inner 2') / / 8
      expect(false).toEqual(false)})})console.log('describe outer-c') / / 5
})
Copy the code

Mocks

In testing, mocks make it easy to test functions that depend on databases, network requests, files, and other external systems. Jest has a built-in mock mechanism that provides a variety of mock methods to address various requirements.

A Mock function

The mock function is very simple and can be obtained by calling jest.fn(). Mock functions have a special.mock attribute that holds information about how the function was called. The.mock property also tracks this on each call.

// mocks/forEach.js
export default (items, callback) => {
  for (let index = 0; index < items.length; index++) {
    callback(items[index])
  }
}

import forEach from './forEach'

it('test forEach function', () = > {const mockCallback = jest.fn(x= > 42 + x)
  forEach([0.1], mockCallback)

// The mock function is called twice
  expect(mockCallback.mock.calls.length).toBe(2)

// The first argument of the first call to the function was 0
  expect(mockCallback.mock.calls[0] [0]).toBe(0)

// The first argument of the second call to the function was 1
  expect(mockCallback.mock.calls[1] [0]).toBe(1)

// The return value of the first call to the function was 42
  expect(mockCallback.mock.results[0].value).toBe(42)})Copy the code

In addition to.mock, Jest also provides matchers to assert the execution of functions, which themselves just check the syntactic sugar of the.mock attribute:

// The mock function was called at least once
expect(mockFunc).toBeCalled();
Copy the code

MockReturnValue and mockReturnValueOnce can be used to mock the return value of a function. When we need to add some logic to a mock function, we can use an implementation of the jest.fn(), mockImplementation, or mockImplementationOnce mock function. You can also name the mock function using mockName; if not named, the output log prints jest.fn() by default.

The Mock timer

Jest can Mock timers to allow us to control “time” in our test code. The jest. UseFakeTimers () function can be used to fake the timer function, the callback function in the timer will not be executed, settimeout. mock can be used to assert the timer execution. When there are multiple timers under test, executing jest. UseFakeTimers () resets the internal counters.

Perform jest. RunAllTimers (); You can “fast forward” until all timers are executed; Perform jest. RunOnlyPendingTimers () can make the current waiting timer implemented, to deal with the timer set the timer in the scene, if use runAllTimers can lead to death cycle; Run jest. AdvanceTimersByTime (msToRun:number) to fast forward the number of milliseconds that can be executed.

The Mock module

There are two main ways to mock a module:

  • usejest.mock(moduleName, factory, options)Automatic mock module, jest will automatically mock for us to specify functions in the module. Among them,factoryoptionsParameters are optional.factoryIs a module factory function that replaces Jest’s automatic mock functionality;optionsTo create a required module that does not exist.
  • If you want to mock your own internal module functions, you can create them in the module level directory__mocks__Directory, and then create mock files for the corresponding modules. For user modules and Node core modules (e.g. Fs, PATH), we still need the calls shown in the test filejest.mock()Other Node modules do not.

In addition, when mock modules are used, jest. Mock () is automatically promoted to be invoked before module import.

Mock for classes is basically the same as mock for modules. Automatic mock, manual mock, and jest. Mock () with module factory arguments are supported. The Jest.

A snapshot of the test

A snapshot test is a great UI test feature provided by Jest. It records a React tree snapshot or other serializable value, compares it to the value of the current test, and gives an error if it doesn’t match. A snapshot should be treated as code, and it needs to be submitted to the repository and reviewed.

If the component rendering results change, the test will fail. We can call jest-u to update the snapshot when the component is properly adjusted. In monitoring mode, we can update snapshots with interactive commands.

Let’s test this with a simple Text component:

// Text.js

import React from 'react'

export default ({className, children}) => {
  return (
    <span className={className}>{children}</span>)}Copy the code

NPM I -d babel-react -react -test-renderer, where babel-react is preset to parse JSX syntax and needs to be added to Babel configuration.

The test code is as follows:

// Text.test.js

import React from 'react'
import renderer from 'react-test-renderer'

import Text from './Text'

it('render correctly', () => {
  const tree = renderer
    .create(<Text className="success">Snapshot testing</Text>)
    .toJSON()
  expect(tree).toMatchSnapshot()
})
Copy the code

After executing the test code, the following snapshot is generated:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render correctly 1`] = `
<span
  className="success"
>
  Snapshot testing
</span>
`;
Copy the code

If subsequent modifications change the component rendering result, the snapshot will not match and the test will fail.

Jest command line

The Jest command-line tool has useful options. Run jest-h to see all the available options. All Jest configuration items can be specified from the command line.

Jest [–config= ] [TestPathPattern] Jest –init Runs tests that match the specified template or file name: jest path/to/my-test.js starts monitoring mode: jest –watch generates coverage reports: jest –coverage

Jest configuration

One of the philosophies of Jest is to provide a fully integrated “zero configuration” test experience, where developers can write test cases directly. It integrates common testing tools for us, mostly using default configurations or a few tweaks.

Jest configuration can be defined in the package. The json or Jest. Config. Js file or from the command line parameters, the config < path/to/js | json >. The configuration is not required. See the documentation for details and use as needed.

The default value of testURL in Jest is about:blank. This error can be avoided if testURL is set to a valid URL in jSDOM, such as http://localhost.