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
-
There is a cost to choosing and learning multiple tools
-
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
-
Matches.js files in the __test__ folder (.jsx.ts.tsx also works)
-
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…