It is a shame to say that I have not written a single line of unit test code in nearly five years of development. Until recently, companies pushed for unit test cases to be written for every project, and for code coverage to have metrics to ensure code quality. As a result, I became the first person in the whole front end group to write unit test cases, so THAT I could explore the holes in advance and gain experience. Now that THE project of writing unit test cases has been completed, I find time to sort out all kinds of problems encountered in “writing unit test cases for the first time in my life” into notes for easy reference in the future.

The front-end framework used by the project is Vue, so the framework selected for the unit test cases is also chosen around the Vue ecology. Read the unit Test section of the Vue tutorial and use Jest and Vue Test Utils at the end. I will walk you through the process of writing unit test cases from the aspects of how to install, how to write, and how to write.

A, install,

The latest version of Jest only supports Babel 7, not Babel 6. Babel 6 has been removed from Jest 24. So if the project is using Babel 6, you need to install Jest version 24 or less, otherwise the project will not run. The next steps will show you how to install Jest in both Babel 6 and Babel 7 environments.

Babel 6 environment

1. Install Jest

npm install --save-dev jest@23.6. 0
Copy the code

Configure to run the test script in the package.json file script tag as follows:

"test:unit": "jest --no-cache"
Copy the code

According to the book Test Vue.js Applications, if you are using Windows, use the no-cache parameter after the jest command to avoid potential errors.

Run the NPM run test:unit command on the console. An error message is displayed:

testMatch: **/__tests__/六四动乱/*.[jt]s? (x), **/? (*) +(spec|test).[tj]s? (x) -0 matches
testPathIgnorePatterns: /node_modules/ - 173 matches
testRegex:  - 0 matches
Pattern:  - 0 matches
Copy the code

No matching test files were found. This is because the Jest framework matches the test files globally throughout the project and then runs the corresponding test cases. __tests__ is the default Jest matching folder where test files are suffixed with.spec.js or.test.js. Therefore, we need to create a folder __tests__ in the project root directory, as well as files with the suffix.spec.js.

Here’s an example:

// sum.js
export const sum = (s1, s2) = > {
	return s1 + s2
}
Copy the code

Unit testing uses the following example:

// __tests__/sum.spec.js
import {sum} from '.. /sum.js'

describe('sum.js'.() = > {
    test('Inputs: 1 and 2; Output: 3 '.() = > {
        expect(sum(1.2)).toBe(3)})})Copy the code

Output result:

From the output, you can see that the unit test case is running properly.

If you want the test case to run automatically whenever a change is made to the test file, you can add a parameter –watch, which is NPM run test:unit — –watch, to the command. Instead of typing watch every time you run the command, you can configure it directly in the package.json file:

{
	"test:unit": "jest --no-cache"."test": "npm run test:unit -- --watch"
}
Copy the code

Simply run the NPM run test command.

2. Install Vue Test Units

Vue Test Unit (Vue Test Unit, Vue Test Unit)

npm install --save-dev jest @vue/test-utils
Copy the code

3. Install vuE-JEST preprocessor

In order for Jest to know how to handle *. Vue files, you need to install and configure the Vue-Jest preprocessor

npm install --save-dev vue-jest
Copy the code

4. Configure Jest

Jest can be configured by creating a separate configuration file or by adding a Jest block to the package.json file. Here, Jest is configured in the package.json file as follows:

// package.json

{
	"jest": {
        "moduleFileExtensions": [
            "js"."json".// tell Jest to process the '*.vue' file
            "vue"]."transform": {
            // Use 'vue-jest' to process '*. Vue' files
            ".*\\.(vue)$": "vue-jest"
        },
        // Handle the Webpack alias
        "moduleNameMapper": {
            "^ @ / (. *) $": "<rootDir>/src/$1".// Function: introduce element-UI, handle CSS module, need to install identity-obj-proxy dependency
            "\\.(css|less)$": "identity-obj-proxy"}}}Copy the code

Configure Babel for Jest

npm install --save-dev babel-jest@23.6. 0
Copy the code

Then add an entry to the package.json file jest. Transform that tells Jest to use babel-jest to process the JavaScript test file

{
	"jest": {
        "transform": {
              // process js with 'babel-jest'
     		"^.+\\.js$": "<rootDir>/node_modules/babel-jest"}}}Copy the code

6. Test coverage

Jest can be configured to output test coverage reports as follows:

{
	"jest": {
		// Enable collection coverage
		"collectCoverage": true.// Specify the test coverage report output path
        "coverageDirectory": "<rootDir>/tests/unit/coverage".// How will the specified test coverage be displayed
        "coverageReporters": [
            "html"."text-summary"].// Specify which files can be collected; Which files do not need to be collected
        "collectCoverageFrom": []}}Copy the code

For more detailed configuration, refer to Jest configuration

7, Jest test single file example

Json file for Jest configuration:

{
	"jest": {
        "moduleFileExtensions": [
            "js"."json"."vue"]."transform": {
            ".*\\.(vue)$": "vue-jest"."^.+\\.js$": "<rootDir>/node_modules/babel-jest"
        },
        "moduleNameMapper": {
            "^ @ / (. *) $": "<rootDir>/src/$1".NPM install --save-dev identity-obj-proxy (NPM install --save-dev identity-obj-proxy)
            "\\.(css|less)$": "identity-obj-proxy"
        },
        "collectCoverage": true."coverageDirectory": "<rootDir>/tests/unit/coverage"."coverageReporters": [
            "html"."text-summary"]."collectCoverageFrom": []}}Copy the code

Test. The vue file:

<template>
    <div>Test.vue</div>
</template>

<script>
export default{}</script>
Copy the code

Test.spec.js Test file

import Test from '.. /Test.vue'
import { mount } from '@vue/test-utils'

console.log(Test)
describe('Test.vue'.() = > {
    test('Render the Test.vue component normally'.() = > {
        const wrapper = mount(Test)
        expect(wrapper.find('div').text()).toContain('Test.vue')})})Copy the code

Vue Test Units provide two methods to mount Vue components, mount and shallowMount. The difference between the two methods is that the mount method parses the entire file, including the child components; ShallowMount parses only the current file, not the subcomponents contained in the current file. You can choose the specific method according to the specific scenario. For more details, refer to the official document Vue Test Utils.

Babel 7 environment

The differences from the Babel 6 environment need to be listed here, otherwise the same.

1. Install Jest

npm install --save-dev jest
Copy the code

2. Configure Babel for Jest

npm install --save-dev babel-core@^7.0. 0-bridge. 0
npm install --save-dev babel-preset-env
npm isntall --save-dev babel-jest
Copy the code

Two, determine the scope

With the environment set up, is it time to start writing test cases? Wait, before we get to that point, we need to determine how to test, that is, which modules need to write test cases. The following is a list of the points that are considered necessary for testing, and the move to the project level can be determined by the actual situation of the specific project.

  • Remove common modules: common functions, common components;

  • Core module: covers all branches.

Three, actual combat cases

This part is mainly about how to write test cases. The specific content is encountered in the project and will be supplemented later.

1. Public function test cases

Testing public functions is relatively simple, after all, relatively independent, and easy to write test cases.

date.js

export const dateFormat = (timestamp) = > {
    const date = new Date(timestamp * 1000)
    const year = date.getFullYear()
    let month = date.getMonth() + 1
    let d = date.getDate()

    month = month < 10 ? '0' + month : month,
    d = d < 10 ? '0' + d : d

    return year + The '-' + month + The '-' + d;
}
Copy the code

date.spec.js

import {dateFormat} from '.. /date'

describe('the dateFormat methods'.() = > {
    test('Enter: time stamp 1611471600; Output: 2021-01-24 '.() = > {
        expect(dateFormat(1611471600)).toContain('2021-01-24')})})Copy the code

2. Vue single file component

Some components may depend on a global plug-in, such as element-UI or vue-Router. To ensure independence from other components, use createLocalVue to archive them and place them in a configuration file: config.js

import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import ElementUI from 'element-ui'

const localVue = createLocalVue()

localVue.use(Vuex)
localVue.use(ElementUI)

if(! process || process.env.NODE_ENV ! = ='test') {
    localVue.use(VueRouter)
}

const router = new VueRouter()

export default {
    router,
    localVue
}
Copy the code

Test.vue

<template>
    <div>
        <el-button type="primary" @click="click">Simulate button clicking</el-button>
    </div>
</template>

<script>
export default {
    name: 'Test',

    data () {
        return {
            count: 0}},methods: {
        click () {
            this.count++; }}}</script>
Copy the code

Test.spec.js

import Test from '.. /Test.vue'
import config from '.. /config'
import { mount } from '@vue/test-utils'

const {localVue} = config

describe('Test.vue'.() = > {
    test('Simulate button clicking'.() = > {
        const wrapper = mount(Test, {
            localVue
        })
        wrapper.find('.el-button').trigger('click')
        expect(wrapper.vm.count).toBe(1)})})Copy the code

3. Simulate AXIOS to send the request

The project inevitably involves sending requests to obtain back-end data, while it is impossible to actually send requests to verify the logic during Jest testing. Therefore, we can simulate the data returned by the interface to test whether our logic is normal. Specific examples are as follows:

Test.vue

<template>
    <div>
        <el-button type="primary" @click="click">Simulate AXIOS to send the request</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'Test',

    data () {
        return {
            list: []}},methods: {
        async click () {
            const result = await axios.get('/list')
            this.list = result.data
        }
    }
}
</script>
Copy the code

Test.spec.js

import Test from '.. /Test.vue'
import config from '.. /config'
import { mount } from '@vue/test-utils'

jest.mock('axios'.() = > {
    return {
        get: jest.fn().mockImplementation(() = > {
            return {
                data: [{id: 1.name: 'username'
                    },
                    {
                        id: 2.name: 'username'}]}})const {localVue} = config

describe('Test.vue'.() = > {
    test('Simulate AXIOS to send a request'.async() = > {const wrapper = mount(Test, {
            localVue
        })
        await wrapper.find('.el-button').trigger('click')
        expect(wrapper.vm.list.length).toBe(2)})})Copy the code

Test the props

Sometimes we need to pass data from parent to child components as props, so how do we test that data is actually being passed? The Vue Test Units framework supports Test props as follows:

Test.vue

<template>
    <div>
        <el-button type="primary" @click="click">Click on the</el-button>
    </div>
</template>

<script>
export default {
    name: 'Test'.props: {
        count: {
            type: Number.default: 0
        }
    },

    data () {
        return {
            result: 0}},mounted(){},methods: {
        click () {
            console.log('Click: '.this.count)
            this.result = this.count * 2}}}</script>
Copy the code

Test.spec.js

import Test from '.. /Test.vue'
import config from '.. /config'
import { mount } from '@vue/test-utils'

const {localVue} = config

describe('Test.vue'.() = > {

    test('Simulate button clicking'.async() = > {const wrapper = mount(Test, {
            localVue,
            propsData: {
                count: 1}})console.log(wrapper.props().count)

        await wrapper.find('.el-button').trigger('click')

        expect(wrapper.vm.result).toBe(2)})})Copy the code

5. Simulate the Window property

The Jest framework does not support the window attribute. That is to say, there is no implementation of the window attribute, so an error will be reported when executing the corresponding code. We can avoid this error by emulating the Window property. Here is an example of emulating window.location.reload as follows:

Test.spec.js

describe('Test the reload method in window Location'.() = > {
  const { reload } = window.location;

  beforeAll(() = > {
    Object.defineProperty(window.location, 'reload', {
      configurable: true});window.location.reload = jest.fn();
  });

  afterAll(() = > {
    window.location.reload = reload;
  });
});
Copy the code

Jest: Testing window.location.reload

Fourth, the collection of problems

Babel 6 will cause an error when installing the latest Jest version

Solution:

npm install --save-dev jest@23.6. 0
Copy the code

Adding Jest to a Babel 6 project

2, babel-Jest version is higher than jest version error

TypeError: Cannot read property 'cwd' of undefined

Solution: Version the same as JEST

3,HandlebarsThe test report cannot be displayed because of a version problem

Handlebars: Access has been denied to resolve the property"from"Because it is not an" own property"of its parent
Copy the code

Solution:

npm i -D handlebars@4.5. 0
Copy the code

Handlebars version problem

4, introduced element- UI CSS parsing error

Solution:

Install identity-obj-proxy:

npm install --save-dev identity-obj-proxy
Copy the code

Then add the configuration to the package.json file jest. ModuleNameMapper:

{
	"jest": {
		"moduleNameMapper": {
			"\\.(css|less)$": "identity-obj-proxy"}}}Copy the code

Error introducing Elder-UI CSS parsing

5. Reference materials

  • Vue Test Units

  • Jest

  • Vue test Guide

  • Vue application test

Because the level is limited, if the above is wrong, welcome to point out, exchange together.