Test JavaScript with Jest

Jest is a library for testing JavaScript code. This is an open source project maintained by Facebook, which is particularly suitable for React code testing, although it is not limited to this: it can test any JavaScript code. The jokes are very fast and easy to use

Introduction to jokes

Jest is a library for testing JavaScript code.

This is an open source project maintained by Facebook, which is particularly suitable for React code testing, although it is not limited to this: it can test any JavaScript code. Its advantages are:

  • It's fast
  • It can executeSnapshot test
  • Be opinionated and provide all ready-made content without you having to make a choice

Jest is a tool very similar to Mocha, although there are differences between them:

  • Mocha is stubborn and Jest has some conventions
  • Mocha requires more configuration, and Jest usually works out of the box, thanks to their self-righteousness
  • Mocha is older and more mature, with more tool integration

In my opinion, the biggest feature of Jest is that it is an out-of-the-box solution that can perform its work without interacting with other test libraries.

installation

Jest will be automatically installed increate-react-app, So if you use it, you don't need to install Jest.

Jest can be installed in any other project using the following commandyarn:

yarn add --dev jest

ornpm:

npm install --save-dev jest

Note how we instructed both parties to put the joke ondevDependenciesa part ofpackage.jsonFile so that it can only be installed in the development environment and not in the production environment.

Add this line to your script sectionpackage.jsonfile:

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

So you can useyarn testornpm run test.

Or, you can install Jest globally:

yarn global add jest

Then usejestCommand line tool.

Create the first Jest test

Project createdcreate-react-appJest is installed and pre-configured out of the box, but adding Jest to any project is as easy as typing

yarn add --dev jest

Add to yourpackage.jsonThis line:

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

And run the test by executingyarn testIn your shell.

Now, you don't have any tests here, so nothing will be performed:

Testing with Yarn

Let's create the first test. Open onemath.jsFile and enter a few functions, we will test them later:

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

module.exports = { sum, mul, sub, div }

Create one nowmath.test.jsFile, placed in the same folder, where we will use Jest to test the functions defined inmath.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) })

Runyarn testCause Jest to run on all the test files found and return the final result to us:

Passing tests

Run Jest with VS Code

Visual Studio Code is an excellent editor for JavaScript development. ThisJoke extensionProvides first-class integration for our testing.

After installation, it will automatically detect whether Jest has been installed in devDependencies and run the test. You can also selectJust kidding: runner-upcommand. Whenever you change one of the files that have tests (or test files), it will run the tests and stay in monitoring mode to rerun them:

A simple Jest test running in VS Code

Matcher

In the previous article, I usedtoBe()As the onlyMatcher:

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

The matcher is a method that allows you to test values.

The most commonly used matcher, compare the value of the resultexpect()Use the value passed as a parameter:

  • toBeStrictly equal, use===
  • toEqualCompare the values of two variables. If it is an object or an array, check the equality of all attributes or elements
  • toBeNullTrue when passing a null value
  • toBeDefinedTrue when the defined value is passed (contrary to the above)
  • toBeUndefinedTrue when passing an undefined value
  • toBeCloseToUsed to compare floating point values to avoid rounding errors
  • toBeTruthyIf the value is considered true, return true (e.g.ifdo)
  • toBeFalsyIf the value is considered false, return true (e.g.ifdo)
  • toBeGreaterThanIf the result of Expect() is higher than the parameter, it is true
  • toBeGreaterThanOrEqualIf the result of Expect() is equal to or higher than the parameter, it is true
  • toBeLessThanIf the result of Expect() is less than the parameter, it is true
  • toBeLessThanOrEqualIf the result of Expect() is equal to the parameter, or less than the parameter, then true
  • toMatchUsed to compare stringsRegular expressionPattern matching
  • toContainUsed in arrays, true if the expected array contains parameters in its element set
  • toHaveLength(number): Check the length of the array
  • toHaveProperty(key, value): Check whether the object has attributes, and optionally check its value
  • toThrowCheck if the function you pass throws an exception (usually) or a specific exception
  • toBeInstanceOf(): Check whether the object is an instance of the class

All these matchers can be used.not.In the statement, for example:

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

Used with promise, you can use.resolveswith.rejects:

expect(Promise.resolve('lemon')).resolves.toBe('lemon')

expect(Promise.reject(new Error(‘octopus’))).rejects.toThrow(‘octopus’)

Set up

Before running the test, you will need to perform some initialization.

To perform an operation before all tests are run, usebeforeAll()Features:

beforeAll(() => {
  //do something
})

To perform certain actions before each test run, usebeforeEach():

beforeEach(() => {
  //do something
})

tear down

Just like you can set it up, you can also perform some actions after each test run:

afterEach(() => {
  //do something
})

After all tests are over:

afterAll(() => {
  //do something
})

Use describe() for group testing

You can create test groups in a single file to isolate the setup and teardown functions:

describe('first set', () => {
  beforeEach(() => {
    //do something
  })
  afterAll(() => {
    //do something
  })
  test(/*...*/)
  test(/*...*/)
})

describe(‘second set’, () => { beforeEach(() => { //do something }) beforeAll(() => { //do something }) test(//) test(//) })

Test asynchronous code

Asynchronous code in modern JavaScript can basically have two forms: callbacks and Promises. In addition to promises, we can also use async / await.

Call back

You cannot test in the callback because Jest will not execute the test-the execution of the test file ends before the callback is called. To solve this problem, pass parameters to the test function, you can call it convenientlydone. I'll wait until you calldone()Before ending the test:

//uppercase.js
function uppercase(str, callback) {
  callback(str.toUpperCase())
}
module.exports = uppercase

//uppercase.test.js const uppercase = require(’./src/uppercase’)

test(uppercase 'test' to equal 'TEST', (done) => { uppercase(‘test’, (str) => { expect(str).toBe(‘TEST’) done() } })

Jest async test callback

committed to

By returning the promised function, weFulfill the promiseFrom the test:

//uppercase.js
const uppercase = str => {
  return new Promise((resolve, reject) => {
    if (!str) {
      reject('Empty string')
      return
    }
    resolve(str.toUpperCase())
  })
}
module.exports = uppercase

//uppercase.test.js const uppercase = require(’./uppercase’) test(uppercase 'test' to equal 'TEST', () => { return uppercase(‘test’).then(str => { expect(str).toBe(‘TEST’) }) })

Jest async test promises

You can use the following methods to test rejected promises.catch():

//uppercase.js
const uppercase = str => {
  return new Promise((resolve, reject) => {
    if (!str) {
      reject('Empty string')
      return
    }
    resolve(str.toUpperCase())
  })
}

module.exports = uppercase

//uppercase.test.js const uppercase = require(’./uppercase’)

test(uppercase 'test' to equal 'TEST', () => { return uppercase(’’).catch(e => { expect(e).toMatch(‘Empty string’) }) })

Jest async test catch

Async/await

To test functions that return promises, we can also use async / await, which makes the syntax very simple and clear:

//uppercase.test.js
const uppercase = require('./uppercase')
test(`uppercase 'test' to equal 'TEST'`, async () => {
  const str = await uppercase('test')
  expect(str).toBe('TEST')
})

Jest async test await async

simulation

In testfling in teethAllows you to test functions that depend on the following functions:

  • database
  • The internetClaim
  • enterArchive file
  • anyExternalsystem

so that:

  1. Your test runFaster, Which provides a fast turnaround time during the development process
  2. Your test isindependentNetwork conditions or database status
  3. Your test is notPollutionAny data storage because they don’t touch the database
  4. Any changes made in the test will not change the state of subsequent tests, and re-running the test suite should start from a known and reproducible starting point
  5. You don't have to worry about rate limiting of API calls and network requests

When you want to avoid side effects (for example, writing to the database) or want to skip slow parts of the code (for example, network access), and also avoid the impact of running the test multiple times (for example, suppose a function The simulation is very useful when sending an incorrect code). Email or call rate-limited API).

More importantly, if you are writingunit test, You should test the function of the function separately, rather than test all the things involved.

Using simulation, you can check whether a module function has been called and which parameters have been used in the following ways:

  • expect().toHaveBeenCalled(): Check whether the spy function has been called
  • expect().toHaveBeenCalledTimes(): Count how many times a spy function has been called
  • expect().toHaveBeenCalledWith(): Check if the function has been called with a specific set of parameters
  • expect().toHaveBeenLastCalledWith(): Check the parameters of the last call to the function

Spy software package without affecting the function code

When importing a package, you can use the following command to tell Jest to "spy" on specific functions:spyOn(), Without affecting the way the method works.

example:

const mathjs = require('mathjs')

test(The mathjs log function, () => { const spy = jest.spyOn(mathjs, ‘log’) const result = mathjs.log(10000, 10)

expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

Simulate the entire package

Jest provides a convenient way to simulate the entire package. Create__mocks__The folder is in the project root directory, and a JavaScript file is created for each package in this folder.

Say you importmathjs. Create__mocks__/mathjs.jsPlace the file in your project root directory and add the following content:

module.exports = {
  log: jest.fn(() => 'test')
}

This will simulate the log() function of the package. Add any number of functions to be simulated:

const mathjs = require('mathjs')

test(The mathjs log function, () => { const result = mathjs.log(10000, 10) expect(result).toBe(‘test’) expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

Simulate a function

You can simulate a single function usingjest.fn():

const mathjs = require('mathjs')

mathjs.log = jest.fn(() => ‘test’) test(The mathjs log function, () => { const result = mathjs.log(10000, 10) expect(result).toBe(‘test’) expect(mathjs.log).toHaveBeenCalled() expect(mathjs.log).toHaveBeenCalledWith(10000, 10) })

You can also usejest.fn().mockReturnValue('test')Create a simple simulation, do nothing except return value.

Pre-built simulation

You can find pre-made models of popular libraries. For example this packagehttps://github.com/jefflau/jest-fetch-mockMake you laughfetch()Call and provide sample return values without interacting with the actual server under test.

Snapshot test

Snapshot testing is a cool feature provided by Jest. It can remember how the UI components are presented and compare it with the current test. If it does not match, an error will be raised.

This is a simple test of a simple App componentcreate-react-appApplication (make sure it's installedreact-test-renderer):

import React from 'react'
import App from './App'
import renderer from 'react-test-renderer'

it(‘renders correctly’, () => { const tree = renderer.create(<App />).toJSON() expect(tree).toMatchSnapshot() })

When running this test for the first time, Jest saves the snapshot to__snapshots__folder. This is what App.test.js.snap contains:

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

exports[`renders correctly 1`] = `
<div
  className="App"
>
  <header
    className="App-header"
  >
    <img
      alt="logo"
      className="App-logo"
      src="logo.svg"
    />
    <h1
      className="App-title"
    >
      Welcome to React
    </h1>
  </header>
  <p
    className="App-intro"
  >
    To get started, edit
    <code>
      src/App.js
    </code>
     and save to reload.
  </p>
</div>
`

As you can see, this is the code rendered by the App component, nothing more.

Compare output for next test<App />To this. If the application changes, an error will occur:

Error with snapshot

when using ityarn testincreate-react-appyou areViewing mode, Then you can presswAnd show more options:

Watch Usage
 › Press u to update failing snapshots.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.

If your change is intended, pressing u will update the failing snapshots, and make the test pass.

You can also update the snapshot by running jest -u (or jest --updateSnapshot) outside of watch mode.


More devtools tutorials: