In computer programming, Unit Testing (English: Unit Testing), also known as module Testing, is a test for the correctness of the program module (the smallest Unit of software design).

A program unit is the smallest testable part of an application. In procedural programming, a unit is a single program, function, procedure, etc. For object-oriented programming, the smallest unit is a method, including methods in a base class (superclass), an abstract class, or a derived class (subclass).

—- from Wikipedia

In layman’s terms, in the front-end world, a unit test is a single module of testing, either a function or a component.

The advantages of unit testing are as follows:

  • Unit testing is an act of validation
  • Unit testing is a design activity
  • Unit testing is the act of writing documentation
  • Unit testing is a regression testing activity

At present, there are many unit testing frameworks, this article will take JEST as an example to discuss.

First experience of Jest

What is the jest

Jest is an elegant and concise JS testing framework that supports Babel, TS, Node, React, Angular, vue, and many other frameworks.

He has the following advantages:

  • Safety fast
  • Complete code coverage with only one command
  • Easy to simulate
  • A friendly error
  • Strong ecology
  • Complete documentation

Environment to prepare

  1. Initialize a project:
npm init
Copy the code
  1. Install the jest
yarn add jest -D
Copy the code

hello world

  1. Add the files you want to test
touch t.js
Copy the code

As follows:

function sum(a, b) {
  return a + b;
}

module.exports = sum;
Copy the code
  1. Creating a Test file
touch t.test.js
Copy the code

The test script is as follows:

const sum = require("./index");

test("adds 1 + 2 to equal 3".() = > {
  expect( sum(1.2) ).toBe(3);
})
Copy the code
  1. Configuration package. Json
{
  ...,
  "scripts": {
    "test": "jest"
  }
}
Copy the code
  1. Run NPM run test

Ii. Jest adapter

In the above example, toBe is a matcher, and the simplest

toBe

ToBe is exact equality, that is x === y

toEuqal

Like toBe, he judges equality, but he judges objects

toBeNull

Result to match NULL

toBeUndefined

The result only matches undefined

toBeDefined

In contrast to the toBeUndefined

toBeTruthy

Matching any if statement to true is essentially expecting true

toBeFalsy

Matching any if statement to false is, in effect, an expected result of false

toBeGreaterThan

When matching numbers, expect greater than result > xx

toBeGreaterThanOrEqual

When matching numbers, expect results >= xx

toBeLessThan

When matching numbers, expect less than, that is, result < xx

toBeLessThanOrEqual

When a number is matched, the expected value is less than or equal to result <= xx

toBeCloseTo

Decimal precision problem matching, there is a classic problem: 0.1 + 0.2! == 0.3, but we expect to be equal, and we need to use toBeCloseTo

toMatch

Used when matching a string that is expected to contain another string.

Example:

expect("abc").toMatch("a") // ok
Copy the code

toContain

Array operations, like indexOf, whether an array contains xx.

Example:

expect( [1.2.3] ).toContain(1) // ok
Copy the code

More and more

IO/zh-hans /doc…

Jest asynchronous code test

Testing the code, note that jEST tests are executed synchronously, and if you write an asynchronous task directly, the test expectation will be executed before the asynchronous task. At this point, we need to execute done manually.

1. Asynchronous functions

const axios = require("axios");
function fetchData() {
  return axios("http://localhost:3001/");
}

module.exports = fetchData;

Copy the code

2. Test asynchronous functions

const fetchData = require("./t");

test("fetch Data".(done) = > {
  fetchData().then((data) = > {
    expect(data.data).toBe("ok");  // ok
    done();
  });
});

Copy the code

Jest Global hook

If we need to do something before we run the test, or after we run the test, we need to do something. Let’s say we need to do something before or after each test case

Well, we can use:

beforeEach

Before each test case, execute


beforeEach(
  () = > console.log("I'm doing it before every test case."))Copy the code

afterEach

After each test case, execute


afterEach(
  () = > console.log("I do it after every test case."))Copy the code

beforeAll

Execute only once before all cases.

beforeAll(
  () = > console.log("Execute once only, before all test cases"))Copy the code

afterAll

Execute after all cases, only once.

afterAll(
  () = > console.log("Execute once only, after all test cases"))Copy the code

Mock functions

We use mock functions when we need to mock callback calls.

demo

Code to be tested:

function sum(a, b, callback) {
  return callback(a + b);
}

module.exports = sum;
Copy the code

Test code:

const sum = require("./t");

test("call".() = > {
  const mockCallback = jest.fn();

  sum(1.2, mockCallback);
  sum(3.4, mockCallback);

  expect(mockCallback.mock.calls[0] [0]).toBe(3);
  expect(mockCallback.mock.calls[1] [0]).toBe(7);
});

Copy the code

The jest. Fn method is the primary use of mock functions, and every such method has the mock attribute.

Mockcallback. mock property, which has the following data structure:

{

  calls: [[3 ], [ 7]],instances: [ undefined.undefined].invocationCallOrder: [ 1.2].results: [{type: 'return'.value: undefined },

    { type: 'return'.value: undefined}}]Copy the code

React + Jest

create / destroy

Each test needs to render to a DOM, which requires a div to be created before each test. When it’s done, delete it.

import { unmountComponentAtNode } from "react-dom";

let container = null;

beforeAll(() = > {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterAll(() = > {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});
Copy the code

act

It ensures that all updates related to module units are processed and applied to the DOM before any assertions are made

The following is an example:

act(
  () = > {
      // Render component})// make an assertion
Copy the code

Render unit test

Components to be tested:

import React from "react";

function Main() {
  return <div>a</div>;
}

export default Main;

Copy the code

Unit test code:

import React from "react";
import { unmountComponentAtNode, render } from "react-dom";
import { act } from "react-dom/test-utils";
import Main from "./t";

let container = null;

beforeAll(() = > {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterAll(() = > {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

test("test".() = > {
  act(() = > {
    render(<Main />, container);
  });

  expect(container.textContent).toBe("a");
});

Copy the code

The event

DOM event test, you can assert the results, using jest. Fn simulation function.

React component:

import React, { useState } from "react";

function Home(props) {
  const [state, setState] = useState(true);

  const handleClick = () = >{ setState(! state); props.onChange(! state); };return (
    <div>
      <button id="btn" onClick={()= > handleClick()}>
        {state ? "a" : "b"}
      </button>
    </div>
  );
}

export default Home;

Copy the code

Test code:

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Home from "./index";

let container = null;
beforeEach(() = > {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() = > {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("Update value when clicked".() = > {
  const onChange = jest.fn();
  act(() = > {
    render(<Home onChange={onChange} />, container);
  });

  const button = document.getElementById("btn");
  expect(button.innerHTML).toBe("a");

  act(() = > {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });

  expect(onChange).toHaveBeenCalledTimes(1);
  expect(button.innerHTML).toBe("b");
});

Copy the code

A snapshot of the test

Jest will save the last snapshot and give diff hints when our component changes.

The original component

import React from "react";

function Home(props) {
  return <div>test</div>;
}

export default Home;

Copy the code

Test code:

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import pretty from "pretty";
import Home from "./index";

let container = null;
beforeEach(() = > {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() = > {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("Update value when clicked".() = > {
  act(() = > {
    render(<Home />, container);
  });

  expect(pretty(container.innerHTML)).toMatchInlineSnapshot(
    Test ` "< div > < / div >" `
  );
});

Copy the code

Run as follows:

When we change the original component code (changing the test copy to Test 1), we run the result again as follows:

7. Report

After you run the Jest test, a Coverage folder appears in your project, which contains the index.html file. When you open it, you can see the specific test report. As follows:

Eight. Summary

Jest is the unit test runner recommended by React. The React framework itself also uses JEST, which is well supported by the community ecosystem.

React test library, that is really delicious ~~

Code word is not easy, please like more, pay attention to ~ 😽