Getting Started
Install Jest using YARN
yarn add --dev jest
Copy the code
Or NPM:
npm install --save-dev jest
Copy the code
Note: Jest documents use the yarn command uniformly, but NPM is also possible. You can see the contrast between YARN and NPM in the YARN documentation.
Let’s start writing tests for a hypothetical function that adds two numbers. First, create a sum.js file:
function sum(a, b) {
return a + b;
}
module.exports = sum;
Copy the code
Then, create a file named sum.test.js. This will include our actual tests:
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Copy the code
Add the following configuration to your package.json:
{
"scripts": {
"test": "jest"}}Copy the code
Finally, run YARN test or NPM run test, and Jest prints the following message:
PASS./sum. Test. Js ✓ Adds 1 + 2 to equal 3 (5ms)Copy the code
You’ve just successfully written your first Jest test!
This test uses Expect and toBe to test that two values are exactly the same.
Run from the command line
You can run Jest directly from the command line (provided that Jest is already in your environment variable PATH, such as Jest installed by YARN Global add Jest or NPM install Jest –global) and specify various useful configuration items for it.
This demonstrates how to run Jest against a file that matches my-test, use config.json as a configuration file, and display a native operating system notification when the run is complete.
jest my-test --notify --config=config.json
Copy the code
If you’d like to learn more about running Jest from the command line, continue reading the JEST CLI Options page.
More configuration
Generate a base configuration file
Based on your project, Jest will ask you a few questions and will create a basic configuration file with a brief description of each option:
jest --init
Copy the code
The use of Babel
If you need to use Babel, you can install the required dependencies through YARN.
yarn add --dev babel-jest @babel/core @babel/preset-env
Copy the code
You can create a babel.config.js file in the root directory of your project to configure Babel compatible with your current Node version:
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',},},],],};Copy the code
The configuration of Babel depends on the specific project usage scenario, and you can consult the official Babel documentation for more details.
Using webpack
Jest can be used in projects that use WebPack to manage resources, styling, and compilation. Webpack presents some unique challenges compared to other tools. Refer to the WebPack guide to get started.
Use the TypeScript
Jest supports TypeScript, via Babel. First make sure you followed the instructions on using Babel above. Next install the @babel/preset-typescript via yarn:
yarn add --dev @babel/preset-typescript
Then add @babel/preset-typescript to the list of presets in your babel.config.js.
Copy the code
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'+}}]'@babel/preset-typescript',]};Copy the code
However, there are a few caveats when working with TypeScript and Babel. Because Babel is required to support TypeScript escape, Jest does not support type checking for tests code. If you want, you can use TS-Jest.
Using Matchers
Jest uses “matchers” to allow you to test your code in a variety of ways. This document will introduce you to some commonly used matchers, a complete list of which can be found in the Documentation of the Expect API.
Ordinary matching machine
The easiest way to test the values is to see if they match exactly.
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
Copy the code
In this code, expect (2 + 2) returns an “expected” object. You usually don’t call too many matchers on these desired objects. In this code,.tobe (4) is the matcher. When Jest runs, it keeps track of all failed matchers so that it can print out good error messages for you.
ToBe uses object. is to test for exact equality. If you want to check the value of an object, use toEqual instead:
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
Copy the code
ToEqual recursively checks each field of an object or array.
You can also test the opposite match:
test('adding positive numbers is not zero', () = > {for (let a = 1; a < 10; a++) {
for (letb = 1; b < 10; b++) { expect(a + b).not.toBe(0); }}});Copy the code
Truthiness
In testing, sometimes you need to distinguish between undefined, null, and false, but sometimes you don’t. Jest lets you figure out what you want.
ToBeNull matches only null toBeUndefined matches only undefined toBeDefined as opposed to toBeUndefined toBeTruthy matches any if statement that is true toBeFalsy matches any if statement Statement is falseCopy the code
Such as:
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();
});
Copy the code
You should use matchers that correspond most precisely to what you want in your code.
digital
Most comparison numbers have equivalent matchers.
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); // toBe and toEqual are equivalentfornumbers expect(value).toBe(4); expect(value).toEqual(4); }); For comparing floating-point equality, use toBeCloseTo instead of toEqual, because you don't want the test to depend on a small rounding error.test('Add two floating point numbers', () => {const value = 0.1 + 0.2; / / expect (value). Place (0.3); ToBeCloseTo (0.3) expect(value).tobecloseto (0.3); });Copy the code
string
You can check for strings with toMatch regular expressions:
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
Copy the code
Arrays and iterables
You can check whether an array or iterable contains a particular item by using toContain:
const shoppingList = [
'diapers'.'kleenex'.'trash bags'.'paper towels'.'beer',];test('the shopping list has beer on it', () => {
expect(shoppingList).toContain('beer');
expect(new Set(shoppingList)).toContain('beer');
});
Copy the code
exception
If the particular function you want to test throws an error, use toThrow when it is called.
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow();
expect(compileAndroidCode).toThrow(Error);
// You can also use the exact error message or a regexp
expect(compileAndroidCode).toThrow('you are using the wrong JDK');
expect(compileAndroidCode).toThrow(/JDK/);
});
Copy the code
And more. These are just a few of them. These are just a few, please refer to the Reference documentation for a complete list of matchers.
Once you’ve learned how to use matchers, learn how Jest allows you to test asynchronous code.
Testing Asynchronous Code
It is common to execute asynchronous code in JavaScript. When you have code running asynchronously, Jest needs to know if the code it is currently testing is complete, and then it can move on to another test. Jest has several ways to handle this situation.
The callback
The most common asynchronous pattern is the callback function.
For example, suppose you have a fetchData(callback) function that fetches some data and calls callback(data) when it’s done. You want to test if the data it returns is ‘peanut butter’.
By default, Jest tests are completed once executed to the end. That means the test will not work as expected:
// Don't do that!test('the data is peanut butter', () = > {function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
Copy the code
The problem is that once the fetchData execution ends, the test ends without calling the callback function.
There is another form of test that addresses this problem. Call done with a single argument, rather than a function that puts the test on an empty argument. Jest will wait for the done callback to finish the test.
// doneIt is very importanttest('the data is peanut butter'.done= > {function callback(data) {
expect(data).toBe('peanut butter');
done(a); } fetchData(callback); });Copy the code
If done() is never called, the test will fail, which is what you want to happen.
Promises
Promises have a more direct way to handle asynchronous testing if you use Promises.
Return a promise in your test, and Jest will wait for that promise to execute Resolve.
If the promise is rejected, the test automatically fails.
For example, if fetchData does not use a callback function, but returns a Promise that resolves to the string ‘peanut butter’, we can test it like this:
test('the data is peanut butter', () = > {return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Copy the code
⸺ if you forget the return statement, the test will be considered complete before the promise returned by fetchData can be executed by resolve and then().
If you want the Promise to be rejected, use the.catch method. Be sure to add expect. Assertions to verify that a certain number of assertions are called. Otherwise, a fulfilled Promise won’t make the test fail
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
Copy the code
.resolves / .rejects
You can also use.converters in expect statements, and Jest will wait for this Promise to resolve. If the promise is rejected, the test automatically fails.
test('the data is peanut butter', () = > {return expect(fetchData()).resolves.toBe('peanut butter');
});
Copy the code
Context If you forget the return statement, the test will be considered finished before fetchData returns a promise that changes to the resolved state and then() can be executed.
If you want the Promise to be rejected, use the.catch method. It referes to engineering. If the Promise is rejected, the test automatically fails.
test('the fetch fails with an error', () = > {return expect(fetchData()).rejects.toMatch('error');
});
Copy the code
Supplement:
Expect. Assertions (number) verifies whether a certain number of assertions have been called during a test. This is often useful when testing asynchronous code to ensure that assertions in the callback are actually invoked.
For example, suppose we have a function, doAsync, that receives two callbacks, Callback1 and callback2, which it will invoke asynchronously in an unknown order. We can use the following methods to test:
test('doAsync calls both callbacks', () => {
expect.assertions(2);
function callback1(data) {
expect(data).toBeTruthy();
}
function callback2(data) {
expect(data).toBeTruthy();
}
doAsync(callback1, callback2);
});
Copy the code
Expect. Assertions (2) Ensure that both callbacks are actually called.
Async/Await
Alternatively, you can use async and await in your tests. You can write an asynchronous test using the async keyword before passing function to the test. For example, it can be used to test the same fetchData scheme
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error'); }});Copy the code
You can use.converor. Rejects to bind async and await
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toThrow('error');
});
Copy the code
In these cases async and await are syntaxes that implement the same logic in promises examples.
None of these forms is particularly superior to the others, and you can mix them throughout your code base or even in a single file. It just depends on which one makes your testing easier.
Actual combat configuration
Install various packages
// identity-obj-proxy An identity object using ES6 proxies. Useful for mocking webpack imports. For instance, you can tell Jest to mock this object as imported CSS modules; then all your className lookups on the imported styles object will be returned as-is. https://github.com/keyz/identity-obj-proxy
// jsdom JSDOM is a JavaScript based headless browser that can be used to create a realistic testing environment. https://github.com/jsdom/jsdom
// JavaScript Testing utilities forReact https://airbnb.io/enzyme/ // https://github.com/facebook/jest // react-test-renderer https://jestjs.io/docs/en/snapshot-testing npm i jest jsdom babel-jest enzyme enzyme-adapter-react-16 React-test-renderer identity-obj-proxy -d // if you want to support ts NPM I ts-jest -d // if you want to support canvas NPM I canvas -dCopy the code
Modify the jest. Config.js configuration
module.exports = {
// collectCoverage: false,
// collectCoverageFrom: ['src/**/*.{ts,tsx}'.'! **/*.d.ts'],
// coveragePathIgnorePatterns: [
// '<rootDir>/src/Task/', / /'<rootDir>/src/Uploader/', / /'<rootDir>/src/utils/ReactVersionWrapper.tsx',
// ],
// coverageDirectory: './tests/coverage',
transform: {
// '^.+\\.(tsx|ts)? $': 'ts-jest'.'^.+\\.(jsx? |scss|js|css$)': 'babel-jest', / /'^.+\\.svg$': '<rootDir>/svgTransform.js',
},
transformIgnorePatterns: ['/node_modules/'], / /testRegex: '(/test/.*|\\.(test|spec))\\.(ts|tsx|js)$',
// moduleFileExtensions: ['ts'.'tsx'.'js'.'json'],
setupFiles: ['<rootDir>/tests/setup.js'],
moduleNameMapper: {
'\\.(scss|less|css)$': 'identity-obj-proxy',
},
// snapshotSerializers: ['enzyme-to-json/serializer'], / /testEnvironment: 'jsdom', / /testResultsProcessor: 'jest-sonar-reporter'};Copy the code
Set up the setup. Js
// JSDOM is a JavaScript based headless browser that can be used to create a realistic testing environment.
// JavaScript Testing utilities for React https://airbnb.io/enzyme/
// https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md
/*
Canvas support
jsdom includes support for using the canvas package to extend any <canvas> elements with the canvas API. To make this work, you need to include canvas as a dependency in your project, as a peer of jsdom. If jsdom can find the canvas package, it will use it, but if it's not present, then
enzyme-adapter-react-16');
const Enzyme = require('enzyme'); Enzyme.configure({ adapter: new Adapter() }); const { JSDOM } = require('jsdom'); const jsdom = new JSDOM('<! doctype html><html><body></body></html>'); const { window } = jsdom; function copyProps(src, target) { Object.defineProperties(target, { ... Object.getOwnPropertyDescriptors(src), ... Object.getOwnPropertyDescriptors(target), }); } global.window = window; global.document = window.document; global.navigator = { userAgent: 'node.js'}; global.requestAnimationFrame = function(callback) { return setTimeout(callback, 0); }; global.cancelAnimationFrame = function(id) { clearTimeout(id); }; copyProps(window, global);Copy the code
Set the Babel. Config. Js
// babel.config.js
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current'}}].'@babel/preset-react']};Copy the code
Take a JSX file and test it out
import React from 'react';
import { mount, shallow } from 'enzyme';
const jsdom = require('jsdom');
import renderer from 'react-test-renderer';
const { JSDOM } = jsdom;
import List from '.. /list';
test('List render correctly', async () => { const dom = new JSDOM(`<! DOCTYPE html><p>Hello world</p>`, { resources:'usable',
runScripts: 'dangerously'}); await sleep(); global.window = dom.window; const tree = renderer.create(<List />).toJSON(); expect(tree).toMatchSnapshot(); });function sleep() {
return new Promise(resolve => {
setTimeout(() => {
resolve(' ');
}, 1500);
});
}
Copy the code
Generate a snapshot
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`List render correctly 1`] = `
<div
className=".blue"
>
aaa1111
<p>
aaaa
</p>
</div>
`;
Copy the code
Reference article:
Jest’s official website