download

  • Running environment node
  • npm install jest -D
  • Change the command in package.json to"test" : "jest"

Supported test file formats (naming conventions)

  • One of two ways
  • He would look for both methods to provide documentation for testing
  • You can do it either way
Test file format 1
  • Support for two suffixes (actually use one, only two suffixes are provided)
    • xxx.spec.js
    • xxx.test.js
Test file format 2
  • create__tests__folder

methods

  • it||test
    • Assertion, used for testing, it and test
    • Two parameters
      • The title (string)
      • The callback (function)
  • describe
    • Use same as above
    • Effect to create a separate scope
  • expect
    • Test values
  • Introduce the module to be tested in the above file
/ /.. /index.js
const add = (a, b) = > a + b;
module.exports = add;

//add.spec.js || add.test.js
const add = require(".. /index");

describe("Add method test -index".() = > {
  test("1 plus 1 should be equal to 2.".() = > {
    const res = add(1.1);
    expect(res).toBe(2);
  });
});
Copy the code

matcher

A parameter
  • toBe
    • Object. Is is used to judge, soWe need absolute equality
  • toEqual
    • Commonly used to check whether reference data types are equal,Do not compare spatial addresses, using recursion,
    • It can also be used to check basic data datatypes
    • In the case of a function, different reference space addresses will also cause the alignment to fail
digital
  • No data type conversion is performed
  • toBeGreaterThanOrEqual
    • Greater than or equal to
  • toBeGreaterThan
    • Less than
  • toBeLessThan
    • Is greater than
  • toBeLessThanOrEqual
    • Greater than or equal to
string
  • toMatch
    • Can be passedregularandstring
    • To pass strings, include is ok, equivalent to includes, butThe difference is that toMatch does not cast data
An array of
  • toContain
    • Check to see if it exists in the array
An error
  • toThrow
    • This parameter is optional
    • Do not fill, is to check whether there is an error message
    • You can enter a type, such as Error or TypeError, which is a check Error type
    • You can fill in a string or re to check the error content
There is no parameter
  • toBeNull
    • Whether to null
  • toBeUndefined
    • Whether it is undefined
  • toBeDefined
    • Not undefined, equal tonot.toBeUndefined
  • toBeTruthy
    • Same as if judgment, can be understood asif(true) === toBeTruthy()
  • toBeFalsy
    • A. if B. if C. if D. ifif(false) === toBeFalsy()

The modifier

  • not
    • On the contrary,

asynchronous

  • Tests for asynchronous methods (no return value)
    • expect.assertions(1)Represents the number of times an asynchronous task has been executed (ensuring that asynchronous methods are executed)
    • expect(true).toBoe(true)Indicates execution (becausetrue === trueSo if I go to this line, I’m done.)
    • Note:In the callback of the test function, use thedoneVariables accept parameters at the end of asynchronous tasksDoing done indicates that we should end the asynchronous task here!
/ /.. /index.js
const delay = (callback) = > {
  setTimeout(() = > {
    callback && callback();
  }, 1000);
};
module.exports = delay;

//__tests__/index.js
const delay = require(".. /index");

test("Callback executed".(done) = > {
  expect.assertions(1);
  const callback = () = > {
    console.log("I execute.");
    expect(true).toBe(true);
    done();
  };
  delay(callback);
});
Copy the code
  • Promise of the test
    • In this case ifDo not accept unless done is used(Otherwise an error will be reported)
    • Finally, you need to implement the promise toReturn (async)
    • You can also test with async and await
    • You can also use jest’s ownresolvesandrejectsMethod to handle promises, used below
const delayPromise = (callback) = > {
  return new Promise((resolve, reject) = > {
    try {
      setTimeout(() = > {
        const res = callback && callback();
        resolve(res);
      }, 1000);
    } catch(e) { reject(e); }}); };// Do not receive if you do not use the done method, otherwise an error will be reported
// We need to return the promise
test("The promise is fulfilled.".() = > {
  expect.assertions(1);
  const callback = () = > 1;
  return delayPromise(callback).then((res) = > {
    expect(res).toBe(1);
  });
});

// Jest's built-in convergent approach
test("Promise uses jest's built-in convergent approach.".() = > {
  expect.assertions(1);
  const callback = () = > 1;
  return expect(delayPromise(callback)).resolves.toBe(1);
});
// Async does not need return
test("Promise async is executed".async () => {
  expect.assertions(1);
  const callback = () = > 1;
  const res = await delayPromise(callback);
  expect(res).toBe(1);
});
Copy the code

mock

Create a mock function
  • jest.fn()
    • Used to create a mock function
    • Parameter: optional (function)
      • A mock instance is returned without passing, and you can do something with some methods on the instance
      • Pass arguments, mock this function, or call instance methods
The mock property of the mock function
  • All mock functions have one.mockProperty (object type), through this property can see how the function is called, etc., to achieve monitoring effect
  • callsGet the information about the function
    • lengthGet the number of times the function is executed
  • resultsRetrieves the result of the specified index (starting with 0)
/ / the mock test
function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) { callback(items[index]); }}const mockCallback = jest.fn((x) = > 42 + x);
forEach([0.1], mockCallback);

it("The mock test".() = > {
  // Get the number of times the function is executed
  expect(mockCallback.mock.calls.length).toBe(2);
  //console.log(mockCallback.mock.calls);
  //calls[0][0], the first [0] represents the first execution, and the second [0] represents the first argument
  // Get the first argument when the function is executed for the first time
  expect(mockCallback.mock.calls[0] [0]).toBe(0);
  // get the first argument for the second execution
  expect(mockCallback.mock.calls[1] [0]).toBe(1);
  // Get the result of the first execution
  expect(mockCallback.mock.results[0].value).toBe(42);
  //[undefined, undefined]
  console.log(mockCallback.mock.instances);
});
Copy the code
  • instancesGet this to the collection
it("Test the mock's this to point to the collection".() = > {
  const myMock = jest.fn();

  const a = new myMock();
  const b = { b: 1 };
  const bound = myMock.bind(b);
  // There will be a second {b: 1} only if the function is executed, because bind just points this to staging, but it hasn't been executed yet
  bound();

  console.log(myMock.mock.instances);
  // > [ mockConstructor {}, { b: 1 } ]
});
Copy the code
  • mockReturnValue()The value returned by the simulated function
    • Just put the specified value inside the parentheses of the mockReturnValue
    • MockReturnValue affects the next test and needs to be usedmockRestoreClearance side effect
// Two are equal
const callback = () = > 1;
const callback = jest.fn().mockReturnValue(1);
Copy the code
  • spyOn
    • Listen for a method or value
    • Two parameters
      • class
      • The method name
    • Hijacking can be done with mockReturnValue
  • mockRestore
    • Clearance side effect
// Returns a random number between 0 and 10
const getRandom = () = > {
  return Math.floor(Math.random() * 10);
};
test("Random number test, less than 10".() = > {
  expect(getRandom()).toBeLessThan(10);
});
test("Random number test, greater than or equal to 0".() = > {
  expect(getRandom()).toBeGreaterThanOrEqual(0);
});
test("Math.random returns 1, resulting in 10".() = > {
  // spyOn listens, then controls the return value using mockReturnValue
  const mockRandom = jest.spyOn(Math."random");
  mockRandom.mockReturnValue(1);
  expect(getRandom()).toBe(10);
  // Use mockRestore to remove side effects. If you do not remove side effects, the next step is 10, which will definitely fail
  mockRandom.mockRestore();
});
test("Does the last step affect me? Less than 10.".() = > {
  expect(getRandom()).toBeLessThan(10);
});
Copy the code
  • mockReturnValueOnce()Same as above, the difference is that it is only simulated once (can be called continuously)
const myMock = jest.fn();

it("Simulated return value".() = > {
  myMock.mockReturnValueOnce(10);

  console.log(myMock(), myMock());
  // > 10, undefined, true, true
});
it("Let's see if it affects the next one.".() = > {
  console.log(myMock());
  //undefined is not affected
});
it("Can be called continuously.".() = > {
  myMock.mockReturnValueOnce(10).mockReturnValueOnce("x").mockReturnValue(true);
  console.log(myMock(), myMock(), myMock(), myMock());
  // 10, 'x', true, true
});
Copy the code
  • mockImplementationOnceSame as above, just to simulate the function, also once
  • mockName
    • Function: Analog name, officially given function is easier to locate error messages
  • getMockName
    • Use this method to get the alias name
const myMockFn = jest
  .fn()
  .mockReturnValue("default")
  .mockImplementation((scalar) = > 42 + scalar)
  .mockName("add42");
myMockFn.getMockName(); //"add42s"
Copy the code
  • Syntactic sugar
it(Grammar candy.() = > {
  myMockFn(1);
  myMockFn();
  //myMockFn is called at least 2 times, rule 1+1,toBeGreaterThan can be negative
  expect(myMockFn.mock.calls.length).toBeGreaterThan(1);
  // The function executes the type at least once with this argument
  expect(myMockFn.mock.calls).toContainEqual([1]);
  // The last time the function was called, the parameter was specified, we did not pass the parameter, so it is [].
  expect(myMockFn.mock.calls[myMockFn.mock.calls.length - 1]).toEqual([]);
  // When the function is called for the first time, the first argument is 1
  expect(myMockFn.mock.calls[0] [0]).toBe(1);
  // Whether the function alias is "add42"
  expect(myMockFn.getMockName()).toBe("add42");
});
Copy the code
Simulation module
  • You need to usejest.mockmethods
// users.js
import axios from "axios";

class Users {
  static all() {
    return axios.get("/users.json").then((resp) = >resp.data); }}export default Users;

//__tests__/mockTest.js
import axios from "axios";
import Users from ".. /src/users";

jest.mock("axios");

test("should fetch users".() = > {
  const users = [{ name: "Bob" }];
  const resp = { data: users };
  axios.get.mockResolvedValue(resp);
  //data is [{name: "Bob"}]
  return Users.all().then((data) = > expect(data).toEqual(users));
});
Copy the code
Jest. Mock can also pass callback
  • When importing a module, you can pass in a callback if you want to do something to the entire module
  • jest.mockThe callback will be up thereThe console. The log before the execution, and callbackThe return value overrides the value received by import, undefined if not returned
  • Used when used in a callbackjest.requireActualMethod to import
//mockObj.js
export const mockObj = {
  a: 1.b: () = > {
    return "b"; }};export default() = >"default";

//__tests__/test.js
import defaultFn from ".. /src/mockObj";
console.log(defaultFn, 2); //{a:9090}
jest.mock(".. /src/mockObj".() = > {
  const old = jest.requireActual(".. /src/mockObj");
  console.log(old, "1");
  /* { default: () => "default", mockObj: { a: 1, b: () => { return "b"; }}} * /
  return {
    a: 9090}; });Copy the code
Batch processing
  • beforeEach/beforeAll
    • BeforeEach executes before a test, once for each test instance execution
    • BeforeAll is executed before the test, but only once, on the normal scope
    • Parameters, callbak
  • afterEach/afterAll
    • Execute after test, same as above
    • Parameters, callbak
    • Usage scenarios
      • One thing is used in multiple tests
/ / before processing
test("Math.random returns 1, resulting in 10".() = > {
  const mockRandom = jest.spyOn(Math."random");
  mockRandom.mockReturnValue(1);
  expect(getRandom()).toBe(10);
  mockRandom.mockRestore();
});
test("Math.random returns 0.1, resulting in 1".() = > {
  const mockRandom = jest.spyOn(Math."random");
  mockRandom.mockReturnValue(0.1);
  expect(getRandom()).toBe(1);
  mockRandom.mockRestore();
});

//可以看到都调用了jest.spyOn(Math, 'random');和mockRandom.mockRestore();
// It can be handled at this point
beforeEach(() = > {
  mockRandom = jest.spyOn(Math."random");
});
afterEach(() = > {
  mockRandom.mockRestore();
});
test("Math.random returns 1, resulting in 10".() = > {
  mockRandom.mockReturnValue(1);
  expect(getRandom()).toBe(10);
});
test("Math.random returns 0.1, resulting in 1".() = > {
  mockRandom.mockReturnValue(0.1);
  expect(getRandom()).toBe(1);
});
Copy the code

Snapshot (testing UI components)

  • Install dependencies
    • npm install @babel/core @babel/preset-env @babel/preset-react react-test-renderer -D
    • npm install react -S
  • create.babelrc
//./babelrc
{
  "presets": ["@babel/preset-env"."@babel/preset-react"]}Copy the code
  • Creating UI Components
//./BaiduLink.js
import React from "react";

const BaiduLink = () = > {
  const url = "https://www.baidu.com";
  const text = "Google it.";
  return <a href={url}>{text}</a>;
};
module.exports = BaiduLink;
Copy the code
  • Create test files
    • We used it herereact-test-rendererThis library helps us generate mapping files
    • usecreateA snapshot is generated.
Outreach snapshot
  • usetoMatchSnapshotCompare old and new mappings
  • Outreach will be generated__snapshots__Folder, which contains the generated mapping file (suffix.snap), written below
//./__tests__/BaiduLink.js
import Link from ".. /BaiduLink";
import renderer from "react-test-renderer";
import React from "react";
it("I'm an outreach snapshot.".() = > {
  const tree = renderer.create(
    <Link page="https://www.baidu.com/">The baidu</Link>
  );
  expect(tree).toMatchSnapshot();
});
Copy the code
  • Start testing,npm test
    • The first test will be in__teste__Directory generation__snapshots__Folder containing the generated mapping files (suffix.snap)
    • Suppose we generate the mapping file and modify it again./BaiduLink.js, execute againnpm testWill throw an exception
      • Because it will compare this mapping to the previous mapping (so, if you create it, make sure to commit the generated mapping file when git add)
    • If you want to override the original mapping, add it in the package.json commandjest --updateSnapshotThen execute (a new mapping is generated and the previous one is overwritten)
Inline snapshot
  • toMatchInlineSnapshot
  • When first used, this method does not pass values
  • When we executenpm testLater,The mapping structure is generated inside the parentheses of the method
  • Instead of generating files externally, it passes them to itself, which is the difference between it and outreach
it("I'm an inline snapshot.".() = > {
  const tree = renderer.create(
    <Link page="https://www.baidu.com/">The baidu</Link>
  );
  // The first time the method does not pass an argument
  expect(tree).toMatchInlineSnapshot();
});

// After the first execution, it places the generated mapping file in function parentheses
Const tree = renderer.create(
       ); Expect (tree).tomatchinlinesnapshot ('   '); }); * /
Copy the code
Create snapshots of unstable data
  • So let’s say I store a piece of datanew DateIt will be different every time, so it will report an error every time
  • This can be done within the toMatchSnapshot function, assert any, and it will save the snapshot for each update without error
it("Test Object Snapshot".() = > {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: "LeBron James"};// If no parameter is passed in the current example, every save after the first one will fail
  expect(user).toMatchSnapshot({
    // Declare it as any
    createdAt: expect.any(Date),
    id: expect.any(Number)}); });Copy the code
Enable test coverage
  • To the end of the package.json test command --coverage
  • After execution, the coverage table is displayed, andAutomatically generate the Coverage folderIt contains detailed information
    • We can open itcoverage/lcov-report/index.htmlFile to view details, clickfileFor the corresponding file, you can see the specific code
      • If it’s a red background it’s not covered
      • The number in front of the line represents how many times this line of code was executed in this test

The react test

@testing-library/react
  • The installationnpm install @testing-library/react @testing-library/jest-dom -D
  • Needed before useSet jEST's environment to JsDOM
  • Create the jest. Config. js file in the root directoryhttps://jestjs.io/docs/configuration
  • content
module.exports = {
  setupFilesAfterEnv: ["@testing-library/jest-dom"].testEnvironment: "jsdom"};Copy the code
  • test
import React from "react";
import { render } from "@testing-library/react";

test("component".() = > {
  const { getByLabelText } = render(<button aria-label="Button" />);
  expect(getByLabelText("Button")).toBeEmptyDOMElement();
});
Copy the code
React-dom comes with a test libraryreact-dom/test-utils
  • You can deconstruct it to get specific methods
  • act
    • One argument, callback
    • Render UI components, not only for the first rendering, but also for state update operations
    • Used before assertion to ensure that our component has been rendered
it("The use of act".() = > {
  act(() = >{reactdom.render (< component />, DOM node); }); });Copy the code
  • Simulate
    • Used to test events, he is better than the nativeMouseEventA lot easier to use
    • Usage:Simulate. Event (node node,{parameters})The second parameter is optional
// Mimic the change event
import React, { useState } from "react";
const Input = () = > {
  const [val, setVal] = useState("12");
  return (
    <input
      type="text"
      className="input"
      value={val}
      onChange={(e)= >{ setVal(e.target.value); // setVal('100'); }} / >
  );
};
export default Input;

// Test the file
import React from "react";
import InputChange from ".. /src/component/InputChange";
import { act, Simulate } from "react-dom/test-utils";
import { render, unmountComponentAtNode } from "react-dom";

let container = null;
beforeEach(() = > {
  // Create a DOM element as the render target
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() = > {
  // Clean up on exit
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("Change event".() = > {
  act(() = > {
    render(<InputChange />, container);
  });
  let input = container.querySelector(".input");
  expect(input.value).toBe("12"); / / success
  act(() = > {
    Simulate.change(input, { target: { value: "123"}}); });console.log(input.value); / / 123
});
Copy the code
MouseEvent
  • Native simulation events, not recommended
  • Function: Tests DOM events
  • Method of use
    • Dom.dispatchevent (new MouseEvent(" event ", {bubbles: true}));
  • Pay attention to{bubbles:true} is required for events to be dispatched so that react can listen to them
  • During the testinnerTextCould not get value, need to usetextContent
//BtnClick.tsx
import React, { useState } from "react";
const BtnClick = () = > {
  const [num, setNum] = useState(0);
  return (
    <>
      <div className="num">Current number {num}</div>
      <div
        className="Btn"
        onClick={()= >{ setNum((old) => { return old + 1; }); }} > Click +1</div>
    </>
  );
};
// interface BtnClick {
// state: any
// setState: any
// }
// class BtnClick extends React.Component {
// constructor(props) {
// super(props);
// this.state = { num: 0 };
// this.handleClick = this.handleClick.bind(this);
/ /}
// handleClick() {
// this.setState(state => ({
// num: state.num + 1,
/ /}));
/ /}
// render() {
// return (
// <>
// 
      
current number {this.state.num}
click +1
// / /) / /} // } export default BtnClick; //test.jsx import React from "react"; import BtnClick from ".. /src/component/BtnClick"; import { render, unmountComponentAtNode } from "react-dom"; import { act, Simulate } from "react-dom/test-utils"; let container = null; beforeEach(() = > { // Create a DOM element as the render target container = document.createElement("div"); document.body.appendChild(container); }); afterEach(() = > { // Clean up on exit unmountComponentAtNode(container); container.remove(); container = null; }); it("Click event".() = > { act(() = > { render(<BtnClick />, container); }); let num = container.querySelector(".num"); console.log(num.textContent); // expect(numValue).tobe (" 0"); act(() = > { container.querySelector(".Btn").dispatchEvent( new MouseEvent("click", { bubbles: true,}));// This is ok //Simulate.click(container.querySelector(".Btn")); }); console.log(num.textContent); }); Copy the code