bzdww

Get answers and suggestions for various questions from here

Front-end test framework Jest

cms
Author introduction: Lin Liehuan, the US group commented on the ordering team members.

Front-end test tools at a glance

The front-end testing tools are also as complex as the front-end framework. Common testing tools can be divided into test frameworks, assertion libraries, and test coverage tools. Before we officially start this article, let's take a general look at them:

Test framework

The role of the testing framework is to provide some convenient syntax for describing test cases and grouping use cases.

There are two types of testing frameworks: TDD (Test Driven Development) and BDD (Behavior Driven Development). I understand that the difference between the two is mainly a grammatical difference, where BDD provides a better readability use case. Grammar, for detailed differences can be found in The Difference Between TDD and BDD .

Common test frameworks are Jasmine , Mocha, and Jest, which is described in this article .

Assertion library

The assertion library mainly provides a semantic method for making various judgments on the values ​​participating in the test. These semantic methods return the results of the test, either success or failure. Common assertions are available in Should.js , Chai.js, etc.

Test coverage tool

Used to test the test case's test of the code and generate a corresponding report, such as istanbul .

Jest

Why choose Jest

Jest is a test framework produced by Facebook. Compared with other test frameworks, it is characterized by built-in common test tools, such as built-in assertion and test coverage tools, which are implemented out of the box.

As a front-end test framework, Jest can use its unique snapshot test function to automatically test common frameworks such as React by comparing snapshot files generated by UI code.

In addition, Jest's test cases are executed in parallel, and only the tests corresponding to the changed files are executed, which improves the test speed. At present, the number of stars on Github has already exceeded 10,000; in addition to Facebook, other companies in the industry have begun to move from other testing frameworks to Jest, such as Airbnb's attempt , and believe that the future Jest development trend will be relatively fast.

installation

Jest can be installed via npm or yarn. Take npm as an example, you can use npm install -g jest for global installation; you can also install only locally and specify the test script in package.json:

{
  "scripts": {
    "test": "jest"
  }
}

Jest's test script name is *.test.js. Whether Jest is run globally or via npm test, it will execute all *.test.js or *.spec.js files in the current directory and complete the test.

Basic use

Use case representation

Indicates that the test case is the most basic API provided by the test framework. Jest internally uses Jasmine 2 for testing, so the use case syntax is the same as Jasmine. The test() function is used to describe a test case, for a simple example:

// hello.js
module.exports = () => 'Hello world'

// hello.test.js
let hello = require('hello.js')

test('should get "Hello world"', () => {
    expect(hello()).toBe('Hello world') // 测试成功
    // expect(hello()).toBe('Hello') // 测试失败
})

Among them toBe ('Hello world') is an assertion (Jest calls it "matcher", want to know more matcher please refer to the documentation ). After writing the use case, run npm test in the project directory to see the test results:



If the test fails, the failed assertion location is identified and the result is as follows:




Pre- or post-processing of use cases

Sometimes we want to check the environment before the test starts, or do some cleanup after the test ends, which requires pre- or post-processing of the use case. The beforeAll() function can be used for uniform preprocessing of all use cases in the test file; if you want to preprocess before each use case starts, you can use the beforeEach() function. As for post-processing, there are corresponding afterAll() and afterEach() functions.

If you just want to do the same pre-processing or post-processing for a few use cases, you can group these use cases first. Use the describe() function to represent a set of use cases, and then put the four handler functions mentioned above into the processing callback of describe() to implement pre- or post-processing of a set of use cases:

describe('test testObject', () => {
    beforeAll(() => {
        // 预处理操作
    })

    test('is foo', () => {
       expect(testObject.foo).toBeTruthy()
    })

    test('is not bar', () => {
        expect(testObject.bar).toBeFalsy()
    })

    afterAll(() => {
        // 后处理操作
    })
})

Test asynchronous code

The key to testing asynchronous code is to tell the test framework when the test is complete and let it assert at the right time. For several common forms of asynchronous code, Jest also provides the corresponding asynchronous test syntax. First, for asynchronous callbacks, pass in and execute the done function, and Jest will wait for the done callback to finish executing and end the test:

// asyncHello.js
module.exports = (name, cb) => setTimeout(() => cb(`Hello ${name}`), 1000)

// asyncHello.test.js
let asyncHello = require('asyncHello.js')

test('should get "Hello world"', (done) => {
    asyncHello('world', (result) => {
        expect(result).toBe('Hello world')
        done()
    })
})

In addition, for asynchronous code controlled by Promise, you can assert directly in the then callback, as long as you are guaranteed to return the Promise object in the use case:

// promiseHello.js
module.exports = (name) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(`Hello ${name}`), 1000)
    })
}

// promiseHello.test.js
let promiseHello = require('promiseHello.js')

it('should get "Hello world"', () => {
    expect.assertions(1); // 确保至少有一个断言被调用,否则测试失败
    return promiseHello('world').then((data) => {
        expect(data).toBe('Hello world')
    })
})

Jest also supports the async/await syntax test, no extra operations are required, as long as the assertion is made after await, which is consistent with the synchronous test.

Test coverage

Jest has a built-in test coverage tool, istanbul . To enable it, you can add the --coverage parameter directly to the command, or configure it in a more detailed configuration in the package.json file .

Running istanbul In addition to the terminal display test coverage, a coverage directory will be produced under the project, with a report of test coverage, so that we can clearly see the test of the branch code. For example, the following example:

// branches.js
module.exports = (name) => {
    if (name === 'Levon') {
        return `Hello Levon`
    } else {
        return `Hello ${name}`
    }
}

// branches.test.js
let branches = require('../branches.js')

describe('Multiple branches test', ()=> {
    test('should get Hello Levon', ()=> {
          expect(branches('Levon')).toBe('Hello Levon')
    });
    // test('should get Hello World', ()=> {
    //       expect(branches('World')).toBe('Hello World')
    // });  
})

Run jest --coverage to see the coverage of the code and the number of untested rows in the production report:



If we remove the comments in branches.test.js and run through all the branches in the test object, the test coverage is 100%:



Used in front-end projects

With React and other frameworks

For the testing of the front-end framework, a feature of Jest is the provision of snapshot testing capabilities. Running the snapshot test for the first time will cause the UI framework to produce a readable snapshot. When testing again, it will determine whether the test passed by comparing the snapshot file with the snapshot generated by the new UI framework. For React, we can produce a snapshot by:

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
    const tree = renderer.create(
        <Link page="http://www.facebook.com">Facebook</Link>
    ).toJSON();
    expect(tree).toMatchSnapshot();
});

Run the test and we can see that a snapshot file is generated as follows:

exports[`renders correctly 1`] = `
<a
    className="normal"
    href="http://www.facebook.com"
    onMouseEnter={[Function]}
    onMouseLeave={[Function]}
>
    Facebook
</a>
`;

This readable snapshot file shows the DOM structure rendered by React in a readable form. Compared to the UI test of the naked eye, the snapshot test is directly compared and faster by Jest; and because the DOM structure is directly displayed, it allows us to quickly and accurately identify the problem while checking the snapshot.

In addition to React, the Jest documentation also provides guidance for testing against other frameworks .

Seamless migration

If you have used other test frameworks in your project, such as Mocha, there is a third-party tool, jest-codemods, that automatically migrates use cases to Jest use cases, reducing migration costs.

Postscript: Front-end automated testing, the value is not worth it?

In recent years, the development of front-end engineering has been surging, but the front-end automated testing seems to be less important. Although there are special testers to test during the project iteration, when the code is ready for testing, the code has been developed. In contrast, if we tested during the development process (either directly using the TDD development model or writing a use case for an existing module), there are the following benefits:

  • Guarantee the integrity of code quality and functionality
  • Improve development efficiency, testing in the development process allows us to find bugs in advance, at this time the speed of problem location and repair is naturally much faster than being called to fix bugs after development.
  • Easy to maintain the project, any subsequent code updates must also run through the test case, even if the refactoring or developer changes can guarantee the realization of the expected function

Of course, everything has two sides. Although the benefits are obvious, not all projects are worth introducing into the testing framework. After all, maintaining test cases is also costly. For content that requires frequent changes and low reusability, such as activity pages, it is not worth the effort to develop a dedicated manpower to write test cases.

And there are probably a few that are suitable for introducing test scenarios:

  • Projects that require long-term maintenance. They need testing to ensure code maintainability and functional stability
  • A more stable project, or a more stable part of the project. Write test cases to them with low maintenance costs
  • Parts that are reused multiple times, such as some common components and library functions. Because of multiple reuse, it is necessary to guarantee quality.

The above is a little insight into my front-end testing. Welcome to the axe.

Ref

Chatting about front-end automated testing