Testing is an important part of software Development, and there is even a test-driven Development model that requires the whole Development work to start from writing Test cases. There are many kinds of classification methods according to different dimensions. According to the testing stage, there are unit test, integration test and system test, and unit test is the most important to ensure the basic correctness of the program.

Unit testing is a way to test the smallest parts of a program to see if the code works as expected. In procedural programming, the smallest is a function. In object-oriented programming, the smallest part is an object method.

This section describes unit testing node.js programs using Jest

Why jEST

The execution of unit tests is often supported by test specifications, assertions, mocks, coverage tools, and so on, and there are many excellent implementations of these tools in the flourishing Node.js ecosystem, but when used together there are two problems

  1. There is a cost to choosing and learning multiple tools

  2. The configuration of combining multiple tools into a specific test solution is complex

Jest is a JavaScript test library for creating, executing, and building test cases. With its own drivers, assertion libraries, mocks, code coverage, and more, Jest is fairly simple to configure and use

Installation and Configuration

$ npm i --save-dev jest
Copy the code

After installing Jest into devDepecencies in your project, add configuration in package.json

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

This allows you to execute the test code using the command NPM test

The jest details can be customized in the jest.config.js file in the root directory, although the jEST details can also be configured in package.json, it is recommended to configure in a separate file for readability

A profound

1. Create a project directory

. ├ ─ ─ the SRC │ └ ─ ─ sum. Js ├ ─ ─ the test │ └ ─ ─ sum. Test. The js ├ ─ ─ the gitignore ├ ─ ─ jest. Config. Js ├ ─ ─ the README. Md └ ─ ─ package. The jsonCopy the code

2. Create the SRC/sum. Js

function sum(a, b) {
  return a + b;
}
module.exports = sum;
Copy the code

3. Create a test/sum. Test. Js

const sum = require('.. /src/sum'); test('1 + 2 = 3', () => { expect(sum(1, 2)).toBe(3); });Copy the code

Use expect(x).tobe (y) in test cases to express x in the same way as y, similar to the assert(x, y) assertion provided by Node.js, and the syntax provided by Jest has better semantics and readability

Execute test command

$ npm test
Copy the code

Jest runs automatically

sum.test.js

File with default matching rules

  1. Matches.js files in the __test__ folder (.jsx.ts.tsx also works)

  2. Matches all files with a.test.js or.spec.js suffix (.jsx.ts.tsx also works)

You can customize the test file matching rules by using the jest. Config. js file in the root directory

module.exports = { testMatch: [/ / glob format "/ __tests__ / * * / * * *. / jt? S (x)", "* * /? (*) + (spec | test). [jt] s?" (x)], / / a regular expression format, and testMatch mutexes, Can't statement / / testRegex: '(/ __tests__ /. * | (\ \ | /) (test | spec)) \ \ [jt] sx? $'};Copy the code

assertions

Jest provides BDD style assertion support and is very feature. here are a few of the most common ones

equal

.tobe () uses object.is to test for exact equality between two values

expect(2 + 2).toBe(4);
Copy the code

If the test object can use toEqual(), recursively check each field of the array or object

const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
Copy the code

Add NOT to express reverse matches

expect(a + b).not.toBe(0);
Copy the code

The true value

  • ToBeNull matches only null

  • ToBeUndefined matches undefined only

  • ToBeDefined is the opposite of toBeUndefined

  • ToBeTruthy matches any if statement as true

  • ToBeFalsy matches any if statement as false

    test(‘null’, () => { const n = null; expect(n).toBeNull(); expect(n).toBeDefined(); expect(n).not.toBeUndefined(); expect(n).not.toBeTruthy(); expect(n).toBeFalsy(); });

    test(‘zero’, () => { const z = 0; expect(z).not.toBeNull(); expect(z).toBeDefined(); expect(z).not.toBeUndefined(); expect(z).not.toBeTruthy(); expect(z).toBeFalsy(); });

digital

test('two plus two', () => { const value = 2 + 2; expect(value).toBeGreaterThan(3); Expect (value). ToBeGreaterThanOrEqual (3.5); expect(value).toBeLessThan(5); Expect (value). ToBeLessThanOrEqual (4.5); });Copy the code

For comparing floating-point equality, use toBeCloseTo instead of toEqual

Test (' add two floating point numbers ', () => {const value = 0.1 + 0.2; Expect (value). Place (0.3); // Expect (value).tobecloseto (0.3); });Copy the code

contains

ToContain can be used to check whether an array or iterable contains a particular item

expect(shoppingList).toContain('beer');
Copy the code

Testing asynchronous functions

Jest provides testing support for several common asynchronous methods

src/async.js

module.exports = { cb: fn => { setTimeout(() => { fn('peanut butter'); }, 300); }, pm: () => { return new Promise(resolve => { setTimeout(() => { resolve('peanut butter'); }, 300); }); }, aa: async () => { return new Promise(resolve => { setTimeout(() => { resolve('peanut butter'); }, 300); }); }};Copy the code

test/async.test.js

const { cb, pm, aa } = require('.. /src/async');Copy the code

The callback

The second function of the test method passes done to indicate that the callback is complete

test('callback data is peanut butter', done => {
  function callback(data) {
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }

  cb(callback);
});
Copy the code

Promise

test('promise then data is peanut butter', () => {
  return pm().then(data => {
    expect(data).toBe('peanut butter');
  });
});
Copy the code

Be sure to return a Promise, or the test session will end before the asynchronous method completes. If you want to test resolve separately, you can use a different writing style

test('promise resolve data is peanut butter', () => {
  return expect(pm()).resolves.toBe('peanut butter');
});
Copy the code

async/await

The async/await test is easy, as long as the outer method is declared async

test('async/await data is peanut butter', async () => {
  const data = await aa();
  expect(data).toBe('peanut butter');
});
Copy the code

Task hooks

Writing test cases often requires some pre-execution before running the test and some cleanup after running the test, and Jest provides helper functions to handle this

Many times over

Use beforeEach and afterEach if you need to perform data initialization beforeEach test task and data cleanup afterEach test task

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});
Copy the code

One-time setting

Use beforeAll and afterAll if the related task needs to be executed globally only once

beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});
Copy the code

scope

By default, blocks of before and after can be applied to each test in the file. Tests can also be grouped through the Describe block. When the before and after blocks are inside the Describe block, they only apply to tests within the Describe block

describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});
Copy the code

mock

In many cases, test cases need to work in the relevant environment, and JEST provides rich environment simulation support

A mock function

You can mock a function using jest.fn(). The mock function has the.mock attribute, which identifies the function being called and the value it returns

const mockFn = jest.fn();
mockFn
    .mockReturnValueOnce(10)
  .mockReturnValueOnce('x')
  .mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
Copy the code

The mock module

Mock (the name of the module) can be used to mock a module, such as a function that relies on Axios to send an asynchronous request. In the actual test, we want to return the result directly without sending the request

// src/user.js const axios = require('axios'); class Users { static all() { return axios.get('/users.json').then(resp => resp.data); } } module.exports = Users; // /src/user.test.js const axios = require('axios'); const Users = require('.. /src/user'); jest.mock('axios'); // mock axios test('should fetch users', () => { const users = [{ name: 'Bob' }]; const resp = { data: users }; / / modify its axios. The get method, direct return results, avoid hair request axios. Get. MockResolvedValue (resp); / / can also simulate the implementation / / axios. Get the mockImplementation (() = > Promise. Resolve (resp)); return Users.all().then(data => expect(data).toEqual(users)); });Copy the code

babel & typeScript

Much of the front-end code now uses ES6 and Typescript directly, and JEST can be easily configured to support it

Install dependencies

$ npm i -D babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest
Copy the code

Add the Babel configuration

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript',
  ],
};
Copy the code

This allows test cases to use ES6 + TypeScript as well

The react test

Install dependencies

$ npm i -S react react-dom
$ npm i -D @babel/preset-react enzyme enzyme-adapter-react-16
Copy the code

Configure the Babel

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript',
    '@babel/preset-react',
  ],
};
Copy the code

Write a component

// src/checkbox-with-label.js import React, { useState } from 'react'; export default function CheckboxWithLabel(props) { const [checkStatus, setCheckStatus] = useState(false); const { labelOn, labelOff } = props; function onChange() { setCheckStatus(! checkStatus); } return ( <label> <input type="checkbox" checked={checkStatus} onChange={onChange} /> {checkStatus ? labelOn : labelOff} </label> ); }Copy the code

Write test cases

React tests can be done in several ways, using the best understood enzyme in the demo

// test/checkbox-with-label.test.js import React from 'react'; import Enzyme, { shallow } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import CheckboxWithLabel from '.. /src/checkbox-with-label'; BeforeAll (() => {// enzyme Initializes the enzyme. Configure ({adapter: new adapter ()}); }) test('CheckboxWithLabel changes the text after click', () => {// Render component const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />); expect(checkbox.text()).toEqual('Off'); Checkbox.find ('input').simulate('change'); expect(checkbox.text()).toEqual('On'); });Copy the code

Test coverage

Jest also provides support for test coverage by executing the command NPM test — –coverage or configuring package.json

  "scripts": {
    "test": "jest",
    "coverage": "jest --coverage"
  }
Copy the code

Run the NPM run coverage command

After executing the command, add the coverage folder in the root directory of the project. Open the coverage/ lcoV-report /index.html file in the browser, and there is a visual test report

Full project code: github.com/Samaritan89…