This article is part of the unit testing series of front-end development tools. It discusses the principles and use of common unit testing tools to ensure correct front-end code. See here for the rest of the series.

This article will be updated from time to time as we explore automated testing, culminating in a full set of automated tests.

There are three types of automated testing

  • A unit test is a test that verifies the correctness of a module, function, or class.
  • Integration test tests multiple modules as a whole.
  • End-to-end E2E end-to-end black-box testing that simulates real users.

For more on testing, see the official descriptions of vue and React

  • vue test
  • react test

1 Unit Test

The logic of unit testing is the same as that of manual testing, that is, executing the logic of a certain unit, and then comparing the execution result with the expected result. If the result is consistent, the test will pass; otherwise, the test will fail. A testing framework usually consists of two parts

  • The Test Runner automatically executes the internal Test code (including the assertion library)
  • Assertion Library, a Library of assertions that determine whether execution results are as expected.

Such as

Test (' The best flavor is estadual ', () => {// Expect (bestLaCroixFlavor()).tobe (' estadual '); // assertion library});Copy the code

The test result will be output automatically after the execution.

Each test file is called a Test suite, and each specific unit test is called a test, or test case. In addition to checking regular algorithms, you can call Snapshots to test your UI.

Jest is recommended by the Front-end testing framework, and is also officially recommended by react and Vue

Also refer to

  • Jest automated testing
  • The realization principle of front-end unit testing framework

2 jest

Like other front-end tools like WebPack, Jest can be executed from the command line or NPM Script, with optional configuration files, various hook functions, and various implementation-specific apis. Think it should be very easy to use, the following to understand.

2.1 Basic Usage

2.1.1 Configuration File

The configuration file is optional and can be created interactively through jest –init with a filename of jest.config.js by default or specified with the –config parameter. See here for configuration options.

NPM scripts can be configured to add “test”: “jest”, and use –watch or –watchAll to automatically test files after modification.

2.1.2 Code organization

Each time jest is executed, jest will look for the relevant test code based on testMatch and testRegex configuration. We chose to create a folder named __tests__, where the.js,.jsx,.ts and.tsx files will be used as test files.

For example, we have a function to test with a path SRC /index.js

export const sum=(a,b)=>a+b
Copy the code

The test file __tests__/sum.test.js, where some of the Jest methods used are global methods and therefore do not need to be explicitly introduced.

import {sum} from '.. /src/index' test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); });Copy the code

Perform ` yarn test

Yarn Run V1.17.3 $jest PASS __tests__3/sum.test.js √ Adds 1 + 2 to Equal 3 (2 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.352s, estimated 2 s Ran all test suites. Done in 2.39s.Copy the code

This is a complete unit test, the smallest unit of our test system.

2.2 expect and mathers

When we write a specific test, we need two parts for comparison, using Expect to match the results to be tested with matchers representing the expected results. The most common expect syntax is expect(value), followed by. Not. Here is mainly about the common matchers, in addition to the following scenarios can be viewed here expect documentation.

Matcher itself is a method of Expect objects.

  1. Common Matchers

The simplest way is exact matching, where toBe uses Object.is, and if you want to check an Object you can use toEqual, which is recursive.

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

Matchers here are used to check various boolean-related values

  • ToBeNull matching null
  • Undefined toBeUndefined matching
  • ToBeDefined the undefined
  • The toBeTruthy if statement is processed as true
  • ToBeFalsy is the opposite
  1. Numbers

Includes matchers for comparison

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 equivalent for numbers expect(value).toBe(4); expect(value).toEqual(4); });Copy the code

For floating-point types, due to error, use

Test ('adding floating point numbers', () => {const value = 0.1 + 0.2; / / expect (value). Place (0.3); Pawnchess's task This won't work because of Pawnerror expect(value). // This works. });Copy the code
  1. Strings

Strings can be matched with re’s

test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

Copy the code
  1. Arrays and iterables

Use toContain to check if an element is contained

  1. Exceptions

Using toThrow

2.3 Testing asynchronous code

For code that executes asynchronously, JEST needs to know the appropriate end in order to execute the next test.

  1. Callbacks

For asynchracy executed as a callback, you cannot check directly in the callback because the current code is complete when jEST execution ends and the callback has not yet been executed. You can add the argument done to the test callback so that done is done when called

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

  fetchData(callback);
});
Copy the code
  1. Promises

If promise is used, Jest will wait until resolved. If rejectD fails, use return. Otherwise, the async process will end before the promise data is returned.

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});
Copy the code
test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});
Copy the code

If the test callback is an async function, asynchrony can also be handled with await.

2.4 Hook Functions

Hooks in asynchrony and others

  1. Each test is executed

Use beforeEach or afterEach

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
  1. Run before all tests for this file start and after all tests finish

In addition to the global scope above, block scopes can be created using the Describe statement, which is executed earlier than tests in other global scopes

2.5 the Mock function

Mock functions can be used to clean up the original implementation of a function, catch function calls, catch new calls to constructors, and so on. There are two ways to mock, either create a mock function using test code or override module dependencies using manual Mock.

2.5.1 Creating mock Functions

You can wrap a function with jest.fn(), and the mock properties of the returned wrapped function will contain information about the state of the call.

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
Copy the code
Sets the return value of the wrapped function
const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

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

When used for mock modules, such as when we want to validate an AXIos request

// users.js
import axios from 'axios';

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

export default Users;
Copy the code

We don’t have to actually call axios, but instead use jest.mock() to rewrite the implementation of the module, for example using mockResolvedValue to mock resolve

// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});
Copy the code
Mock full implementation
// foo.js module.exports = function () { // some implementation; }; // test.js jest.mock('.. /foo'); // this happens automatically with automocking const foo = require('.. /foo'); // foo is a mock function foo.mockImplementation(() => 42); foo(); / / > 42Copy the code
Custom matcher

The MockFunc returned earlier via jest.fn() can be customized

2.5.2 Manual away,

Mainly used to simulate data access, such as reading local files. See here for details

2.6 the Snapshot

A snapshot is used to determine if the UI has changed, for example to test the React component. This method does not render the entire app but serializes the React Tree. Let’s say I have a component

import React from 'react'

export const List=()=>{
    return <span>5555</span>
}
Copy the code

Corresponding test file

import React from 'react'; import renderer from 'react-test-renderer'; import {List} from '.. /src/index'; it('renders correctly', () => { const tree = renderer .create(<List></List>) .toJSON(); expect(tree).toMatchSnapshot(); });Copy the code

After the test is executed, a Snap file is generated to represent the current snapshot. If the snapshot is different, an error message is displayed. To update a snapshot, use jest –updateSnapshot

2.7 dom manipulation

Jest polyfills a browser API in Node to manipulate the DOM directly.

2.8 jest object

Jest objects are also in the global scope, where a number of properties and methods are mounted, including the following classes

  • Mock Modules
  • Mock functions
  • Mock timers
  • Misc

And the flower