This is the 125th original article without water. If you want to get more original articles, please search our official account to follow us. This article was first published on the front end blog of Political Research Cloud: How to do front-end unit testing

How to do front-end unit testing

For today’s front-end engineering, a standard complete project, unit testing is often necessary. But a lot of times we just finish the project and ignore the project testing. I think one of the big reasons is that many people don’t know enough about unit testing, so I wrote this article, on the one hand, hoping to give you a preliminary understanding of unit testing through this article. The other aspect is that code examples will give you a hands-on ability to write unit tests.

Why does the front end need unit tests?

  1. Necessity: JavaScript lacks type checking, errors cannot be located during compilation, and unit tests can help you test for a variety of exceptions.

  2. Correctness: Testing validates the correctness of the code before it goes live.

  3. Automation: You can print out internal information through the console, but this is a one-time thing and the next test needs to be done from scratch, so efficiency is not guaranteed. By writing test cases, you can write them once and run them many times.

  4. Ensure refactoring: The Internet industry product iteration speed is very fast, there must be a process of code refactoring after iteration, then how to ensure the quality of the refactoring code? With test cases behind you, you can be bold about refactoring.

The status quo

Here is a sample survey based on the following:

  • An online questionnaire was sent to 200 stakeholders, and 70 of them answered the questions in the questionnaire, accounting for 81.16% of the front end. If you are interested, you can also help me fill in the questionnaire

  • Data collection date: 2021.09.21 — 2021.10.08

  • Target audience: all developers

  • Organization size: less than 50, 50 to 100, 100 +

Have you ever performed a JavaScript unit test?

Another interesting insight from the survey is that unit testing is more popular in large organizations. One reason may be that large organizations deal with large-scale products and frequent functional iterations. This continuous iterative approach forces them to invest in automated testing. More specifically, unit testing helps enhance the overall quality of the product.

In addition, the report shows that more than 80% believe that unit testing can effectively improve quality, more than 60% have used Jest to write front-end unit tests, and more than 40% believe that unit test coverage is important and should be greater than 80%.

Common unit testing tools

Currently, Mocha and Jest are the most popular front-end unit testing frameworks, but I recommend you to use Jest, because Jest has obvious advantages over Mocha in terms of the number of Starts & Issues and NPM downloads from Github.

Github stars and NPM downloads in real time, see Jest vs Mocha screenshot dated 2021.11.25

Github stars & issues

NPM downloads

Jest has been downloaded in part because the create-React-app scaffolding has Jest built in by default, and most React projects are built with it.

In terms of Github Starts & Issues and NPM downloads, Jest has a higher profile and a more active community

Framework of contrast

The framework assertions asynchronous Code coverage
Mocha Not supported (requires support from other libraries) friendly Not supported (requires support from other libraries)
Jest The default support friendly support
  • Mocha has a good ecosystem, but requires a lot of configuration to achieve high scalability
  • Jest out of the box

For example, write a use case for the sum function

./sum.js

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

module.exports = sum;
Copy the code

Mocha + Chai

Mocha needs to introduce CHAI or some other assertion library to assert, and if you need to view coverage reports you need to install NYC or some other coverage tool

./test/sum.test.js

const { expect, assert } = require('chai');
const sum = require('.. /sum');

describe('sum'.function() {
  it('adds 1 + 2 to equal 3'.() = > {
    assert(sum(1.2) = = =3);
  });
});
Copy the code

Jest way

Jest supports assertions by default as well as coverage testing by default

./test/sum.test.js

const sum = require('./sum');

describe('sum function test'.() = > {
  it('sum(1, 2) === 3'.() = > {
    expect(sum(1.2)).toBe(3);
  });
  
  // There is no obvious difference between test and it. It means: it should be XXX
  test('sum(1, 2) === 3'.() = > {
    expect(sum(1.2)).toBe(3);
  });
})
Copy the code

Jest has a huge advantage both in popularity and writing style, so it is recommended that you use Jest out of the box

How to start?

1. Install dependencies

npm install --save-dev jest
Copy the code

2. Simple examples

First, create a sum.js file

./sum.js

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

module.exports = sum;
Copy the code

Create a file named sum.test.js that contains the actual test contents:

./test/sum.test.js

const sum = require('.. /sum');

test('adds 1 + 2 to equal 3'.() = > {
  expect(sum(1.2)).toBe(3);
});
Copy the code

Add the following configuration section to your package.json

{
  "scripts": {
    "test": "jest"}},Copy the code

Run NPM Run test and Jest will print the following message

3. Some ES6 syntax is not supported

Nodejs uses the CommonJS modular specification and uses require to introduce modules. Import is the ES6 modularity specification keyword. To use import, you must introduce Babel escape support, compile through Babel, and make it modular code for Node

If the following files are written in ES6 format, an error occurs when you run NPM run test

./sum.js

export function sum(a, b) {
  return a + b;
}
Copy the code

./test/sum.test.js

import { sum } from '.. /sum';

test('adds 1 + 2 to equal 3'.() = > {
  expect(sum(1.2)).toBe(3);
});
Copy the code

An error

In order to use these new features, we need to use Babel to convert ES6 to ES5 syntax

The solution

Install dependencies

npm install --save-dev @babel/core @babel/preset-env
Copy the code

Add the root directory to.babelrc

{   "presets": ["@babel/preset-env"]}Copy the code

Run NPM run test again and the problem is resolved

The principle of

The jEST runtime executes internally (jest-babel) to check if babel-core is installed, and then takes the configuration from.babelrc. The test case code is converted with Babel before running the test

4. Test ts files

Jest needs.babelrc to parse TypeScript files for testing

Install dependencies

npm install --save-dev @babel/preset-typescript
Copy the code

Rewrite the * * * *. Babelrc

{   "presets": ["@babel/preset-env"."@babel/preset-typescript"]}Copy the code

You’ll also need to install it in order to resolve editor errors about the type of Jest assertion methods, such as Test and Expect

npm install --save-dev @types/jest
Copy the code

./get.ts

/** * access nested objects to avoid code like user && user.personalInfo? User.personalinfo. name: null code */
export function get<T> (object: any, path: Array<number | string>, defaultValue? : T) : T {
  const result = path.reduce((obj, key) = >obj ! = =undefined ? obj[key] : undefined, object);

  returnresult ! = =undefined ? result : defaultValue;
}
Copy the code

./test/get.test.ts

import { get } from './get';

test('Test the existence of an enumerable property line1 for nested objects'.() = > {
  expect(get({
    id: 101.email: '[email protected]'.personalInfo: {
      name: 'Jack'.address: {
        line1: 'westwish st'.line2: 'washmasher'.city: 'wallas'.state: 'WX'}}},'personalInfo'.'address'.'line1'])).toBe('westwish st');
});
Copy the code

Run NPM run test

5. Keep listening

For efficiency, you can add startup parameters to allow JEST to continuously listen for changes to the file, rather than having to re-execute the test case after each change

Rewrite the package. The json

"scripts": {     "test": "jest --watchAll"   },
Copy the code

The effect

5. Generate test coverage reports

What is unit test coverage?

Unit test coverage is a measure of software testing. It refers to the percentage of functional code that has been unit-tested. There are a number of automated test framework tools that can provide this statistic. The most basic calculation is:

Unit test coverage = lines of code under test/total lines of reference *100%
Copy the code

How do you generate it?

Add the jest. Config.js file

module.exports = {
  // Whether to display coverage report
  collectCoverage: true.// Tell Jest which files need to be unit tested
  collectCoverageFrom: ['get.ts'.'sum.ts'.'src/utils/**/*'],}Copy the code

Rerun effect

Parameter interpretation

Parameter names meaning instructions
% stmts Statement coverage Is every statement executed?
% Branch Branch coverage Is every if block executed?
% Funcs Function coverage Is every function called?
% Lines Line coverage Is every line executed?

Set the unit test coverage threshold

In my opinion, since unit testing is integrated in the project, it is very necessary to pay attention to the quality of unit testing, and the coverage rate objectively reflects the quality of single testing to some extent. Meanwhile, we can also remind users whether the expected quality is achieved by setting the unit testing threshold.

Jest. Config. Js file

module.exports = {
  collectCoverage: true.// Whether to display coverage report
  collectCoverageFrom: ['get.ts'.'sum.ts'.'src/utils/**/*'].// Tell Jest which files need to be unit tested
  coverageThreshold: {
    global: {
      statements: 90.// Ensure that each statement is executed
      functions: 90.// Make sure every function is called
      branches: 90.// Make sure that every branch such as if executes}},Copy the code

The threshold above requires that our test cases be sufficient, and if our use cases are not sufficient, the following error will help you improve

6. How to write unit tests

Let’s use the fetchEnv method as an example to write a complete set of unit test cases for readers’ reference

Write the fetchEnv method

The. / SRC/utils/fetchEnv ts file

/** * Environment parameter enumeration */
 enum IEnvEnum {
  DEV = 'dev'./ / development
  TEST = 'test'./ / test
  PRE = 'pre'./ / pretest
  PROD = 'prod'./ / production
}

/** * Get the current environment parameters * from the link@param {string? } Url resource link *@returns {IEnvEnum} Environment parameters */
export function fetchEnv(url: string) :IEnvEnum {
  const envs = [IEnvEnum.DEV, IEnvEnum.TEST, IEnvEnum.PRE];

  return envs.find((env) = > url.includes(env)) || IEnvEnum.PROD;
}
Copy the code

Write corresponding unit tests

. / test/fetchEnv. Test. Ts file

import { fetchEnv } from '.. /src/utils/fetchEnv';

describe('fetchEnv'.() = > {
  it ('Determine whether dev environment'.() = > {
    expect(fetchEnv('https://www.imooc.dev.com/')).toBe('dev');
  });

  it ('Test the environment'.() = > {
    expect(fetchEnv('https://www.imooc.test.com/')).toBe('test');
  });

  it ('Check whether pre environment is available'.() = > {
    expect(fetchEnv('https://www.imooc.pre.com/')).toBe('pre');
  });

  it ('Determine whether to prod the environment'.() = > {
    expect(fetchEnv('https://www.imooc.prod.com/')).toBe('prod');
  });

  it ('Determine whether to prod the environment'.() = > {
    expect(fetchEnv('https://www.imooc.com/')).toBe('prod');
  });
});
Copy the code

The execution result

7. Common assertion methods

There are many assertion methods, but here are just some common ones. If you want to know more about them, you can check out the API section of the Jest website

The.not modifier allows you to test for cases where the result is not equal to a value

./test/sum.test.js

import { sum } from './sum';

test('Sum (2, 4) does not equal 5'.() = > {
  expect(sum(2.4)).not.toBe(5);
})
Copy the code

The. ToEqual matcher recursively checks whether all attributes of an object and their values are equal, often to detect reference types

./src/utils/userInfo.js

export const getUserInfo = () = > {
  return {
    name: 'moji'.age: 24,}}Copy the code

./test/userInfo.test.js

import { getUserInfo }  from '.. /src/userInfo.js';

test('getUserInfo() returns objects of equal depth '.() = > {
  expect(getUserInfo()).toEqual(getUserInfo());
})

test('getUserInfo() returns a different object memory address '.() = > {
  expect(getUserInfo()).not.toBe(getUserInfo());
})
Copy the code

ToHaveLength is a handy way to test whether the length of a string or array type meets expectations

./src/utils/getIntArray.js

export const getIntArray = (num) = > {
  if (!Number.isInteger(num)) {
    throw Error('getIntArray' only accepts integer arguments');
  }
  
  return [...new Array(num).keys()];
};

Copy the code

./test/getIntArray.test.js

./test/getIntArray.test.js
import { getIntArray }  from '.. /src/utils/getIntArray';

test('getIntArray(3) should return an array of 3'.() = > {
  expect(getIntArray(3)).toHaveLength(3);
})
Copy the code

ToThorw allows us to test whether the method under test throws an exception as expected

Note, however, that we must use a function to wrap the function being tested, as getIntArrayWrapFn does below, otherwise the assertion will fail because the function threw an error.

./test/getIntArray.test.js

import { getIntArray }  from '.. /src/utils/getIntArray';

test('getIntArray(3.3) should throw an error '.() = > {
  function getIntArrayWrapFn() {
    getIntArray(3.3);
  }
  
  expect(getIntArrayWrapFn).toThrow('getIntArray' only accepts integer arguments');
})

Copy the code

.tomatch passes in a regular expression that allows us to do string matching

./test/userInfo.test.js

import { getUserInfo }  from '.. /src/utils/userInfo.js';

test("GetUserInfo ().name should contain 'mo'".() = > {
  expect(getUserInfo().name).toMatch(/mo/i);
})
Copy the code

Testing asynchronous functions

./servers/fetchUser.js

/** * Get user information */
export const fetchUser = () = > {
  return new Promise((resole) = > {
    setTimeout(() = > {
      resole({
        name: 'moji'.age: 24,}}),2000)})}Copy the code

./test/fetchUser.test.js

import { fetchUser } from '.. /src/fetchUser';

test('fetchUser() can request a user named moji'.async() = > {const data =  await fetchUser();

  expect(data.name).toBe('moji')})Copy the code

Here you might see an error like this

This is because @babel/preset-env does not support async await. In this case, the Babel configuration needs to be enhanced. Install @babel/ plugin-transform-Runtime

npm install --save-dev @babel/plugin-transform-runtime
Copy the code

Also rewrite.babelrc

{
  "presets": ["@babel/preset-env"."@babel/preset-typescript"]."plugins": ["@babel/plugin-transform-runtime"]}Copy the code

Run again and there will be no errors

ToContain Whether the matched object contains

./test/toContain.test.js

const names = ['liam'.'jim'.'bart'];

test('Does the matched object contain?'.() = > {
  expect(names).toContain('jim');
})
Copy the code

Check for special values (NULL, undefined, and Boolean)

ToBeNull matches onlynullToBeUndefined matches onlyundefinedToBeDefined with... Instead, toBeUndefined toBeTruthy matchesifStatement astrueAny content toBeFalsy matchesifStatement asfalseToBeGreaterThan greater than toBeGreaterThanOrEqual toBeLessThan less than toBeLessThanOrEqual most (less than or equal to toBeLessThanOrEqual) ToBeCloseTo is used to match floating point numbers (equality with decimal points)Copy the code

conclusion

That’s all. After reading this article, you’ll have mastered the basics of front-end unit testing, and you can even follow the steps of the article to incorporate unit testing into your project. In the meantime, if you have any questions or better insights or better framework recommendations during the reading process, feel free to leave a comment in the comments section!

You may have mastered front-end unit testing before you read this article, or even become a leader in the field. First of all, I feel honored and sincerely invite you to submit your valuable comments in the comments section. Thank you in advance.

Finally, thank you for taking time out of your busy schedule to read this article. I will send roses to you with lingering fragrance in my hands. If you think this article is helpful to you, I hope you can give me a thumbs up!

reference

Brief introduction to front-end unit testing

Jest official documentation

Recommended reading

  • Sketch Plug-in Development Guide
  • Why is index not recommended as key in Vue
  • Brief analysis of Web screen recording technology scheme and implementation

Open source works

  • Political cloud front-end tabloid

Open source address www.zoo.team/openweekly/ (wechat communication group on the official website of tabloid)

  • Item selection SKU plug-in

Open source addressGithub.com/zcy-inc/sku…

, recruiting

ZooTeam, a young passionate and creative front-end team, belongs to the PRODUCT R&D department of ZooTeam, based in picturesque Hangzhou. The team now has more than 60 front-end partners, with an average age of 27, and nearly 40% of them are full-stack engineers, no problem in the youth storm group. The members consist of “old” soldiers from Alibaba and netease, as well as fresh graduates from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. In addition to daily business docking, the team also carried out technical exploration and practice in material system, engineering platform, building platform, performance experience, cloud application, data analysis and visualization, promoted and implemented a series of internal technical products, and continued to explore the new boundary of front-end technology system.

If you want to change what’s been bothering you, you want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the pace, it will be “5 years and 3 years of experience”; If you want to change the original savvy is good, but there is always a layer of fuzzy window… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a front end team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]