Article tone
- Introduce the concept and thinking process, and do not provide code (refer to the specific code writing method) jest official website)
Extension:
- In the era of information explosion, all kinds of resources are very rich. There are many materials on the Internet
- However, the details of the official website do not duplicate the same information, resulting in additional mental burden
- The brain is just a search engine. It knows where to find resources and is not responsible for recording specific practices to save memory
Several names of test
- Visual test: [test tool] the front-end vision is relatively changeable, so the cost of visual test is large and the popularity is not high, but the advantage is that it can test the style information
- Unit test: [test objective] the test of minimum granularity, which is suitable for the test of function library, basic component library, etc. for a single function or function
- Integration test: [test objective] simulate the user's operation, face the final result of delivery, and focus on the process of the project
- TDD (Test Driven Development): [methodology] first write test cases (put forward expectations), and then write specific implementation methods and functions for unit testing
- BDD (Behavior Driven Development): [methodology] Based on integration testing
This paper mainly introduces jest (joke) unit test library
Principles and limitations of jest unit testing
I hope you can understand the principle, what the scope is and whether you can do it first
jest runs on the node side. The underlying implementation library is jsdom. Node is used to simulate a set of dom environment. The scope of simulation is limited to dom hierarchy and operation
[dom operation] it only simulates most of the general functions of dom, but some specific dom APIs do not support it, such as the media function api of canvas and video
- If you want to test the media API of canvas and video, you need to install the corresponding extension library, which can be understood as realizing the functions of the browser on the node side, such as image generation, audio and video playback, etc
- canvas extension , video related extensions are not found yet
[css style] strictly speaking, there is no css style simulation function. css is only regarded as a pure dom attribute string in jsdom, which is no different from id and class strings
- Inheritance is not supported. Each dom is an independent individual without style inheritance.
- Only inline styles are supported , unrecognized style in vue
- Less useful, an example of parsing the outer chain style
- Here's one Solution , but there was no official merger
- Non inline style test, need to use Visual test library
What scenarios do unit tests need to cover?
Code change
- You can find it by running unit tests directly, but how to avoid developers forgetting to run unit tests?
- It is solved by adding cicd process. When submitting the merge request application, the unit test is triggered. If the operation fails, the merge request is automatically rejected, and the node command is executed to send a message reminder
- gitlab ci configuration will be introduced at the end of the article
New code
- New functions or functions will not be covered by running old unit tests. How to remind developers to cover the newly added code?
- Solve the problem by configuring 100% of the test coverage lines. If the target is not reached, it will be regarded as failing the test to avoid the omission of new code. How to solve branches or functions that cannot be covered?
- By configuring "ignore comments / istanbul ignore next /", maintain the 100% coverage test of a file
If you have time later, you can also search these ignore configurations globally to cover the tests one by one and play the role of marking
coverageThreshold: { './src/common/js/*.js': { branches: 80, // Percentage of code logical branches covered functions: 80, // Percentage of override function lines: 80, // Percentage of code lines covered statements: -10 // If there are more than 10 uncovered statements, jest will fail } },
Add new documents and whether the test is omitted
- In general, unit tests only run unit test files. If there is no corresponding test file in the newly added code file, there will be missed tests
Specify the folder to be overwritten through the collectCoverageFrom parameter. When there is no corresponding test case for the file in the folder, it will be treated as coverage 0 to remind the missing test of new files
// Generate coverage information from those folders, including files for which test cases are not set, and solve the test coverage problem of missing new files collectCoverageFrom: [ './src/common/js/*.{js,jsx}', './src/components/**/*.{js,vue}', ],
Special scenarios (value of experience)
- For some functions, there is no problem in normal operation, and errors will be reported only in special cases. For example, for simple addition operation, there will be calculation error in decimal, 0.1 + 0.2 = 0.300000000000000 4
- The coverage of these special scenes can only be recorded by front-line developers in their actual work, which requires the accumulation of time
- This is the value of programmer experience, and it is also a rare part that is worth inheriting
Unit test ignore principle
The istanbul library is used at the bottom of jest collection coverage (istanbul: istanbul, Shengsheng carpet, carpet for coverage). The following ignored formats are the functions of istanbul library
- Ignore this file and put it at the top of the file/
- Ignore a function, a piece of branch logic or a line of code and put it at the top of the function / istanbul ignore next/
- Ignore function parameter default value function getWeekRange(/* istanbul ignore next */ date = new Date()){
- Specific ignore rules can be viewed Introduction to istanbul github
Correct posture for writing test cases
Take the expectation and positioning of the function as the starting point, not the code. At the beginning, we should first think about the functions that the function or tool library needs to play, rather than the code at the beginning
- First list the functions of the component or function you expect, and write it in text. This is also the function described in test('detect click event ') to inform others of the purpose of this test case
- Write corresponding test cases
- Modify the code that does not meet the test case
- Observe code coverage and cover all code lines
Add jest global custom function
- If the frequency of a test function is relatively high, you can consider aligning and reusing it, write a preloaded file, and load the file before each test file is executed
- For example, it is cumbersome to obtain the original code of dom style, wrapper element. style. Height, and element has not been officially exposed, which is an internal variable
You can add configuration files, write styles global methods, and obtain style data through functions, which is consistent with methods such as classes
// jest.config.js sets the pre run file, which will be run before each test file is executed to add some global methods setupFilesAfterEnv: ['./jest.setup.js'],
// ./jest.setup.js import { shallowMount } from '@vue/test-utils' // Mount the general-purpose function styles to the global wrapper and return the inline style of the element (because jsdom only supports inline styles and does not support detecting styles in class), or the value of an inline style function addStylesFun() { // Generate a temporary component, obtain vueWrapper and domWrapper instances, and mount the style method const vueWrapper = shallowMount({ template: '<div>componentForCreateWrapper</div>' }) const domWrapper = vueWrapper.find('div') vueWrapper.__proto__.styles = function(styleName) { return styleName ? this.element.style[styleName] : this.element.style } domWrapper.__proto__.styles = function(styleName) { return styleName ? this.element.style[styleName] : this.element.style } } addStylesFun()
Hook function
Similar to the guard function in vue router, the hook function is executed before and after entering
- Solve the problem of data storage of stateful functions and avoid repeatedly writing code to prepare data when executing each test case
beforeAll,afterAll
- If it is written in the outermost part of the unit test file, it means that the function is executed once before and after the file is executed
- It is written in the outermost layer of the test group describe, which means that the function is executed once before and after the test group is executed
beforeEach,afterEach
- Each test case is executed once before and after
Quick unit testing skills
Skip the use cases that have been tested successfully and the source code has not changed, and there is no need to execute them
In the first step, if the jest --watchAll test file changes, the test will be executed automatically
- This parameter can only be added in the package script command. It will not take effect after the npm command is executed
- Source code change or unit test file change will trigger
Step 2, press f (execute only the wrong use case)
- The disadvantage is that the changes of the successfully executed unit tests and the corresponding source code cannot be monitored (that is, the previously successful ones will be ignored, regardless of the new changes and whether there are errors)
- Source code change or unit test file change will trigger
- You can switch the global traversal by pressing f repeatedly
Step 3, press o again (only execute the test case of the file whose source code has changed)
- Equivalent to jest --watch
- Only listen to the files in git that are not submitted to the temporary storage area. Once the stash is submitted, it will not be triggered
- Even if there are failed test cases in this file, they will be ignored
- Press o to switch the test case file with a
- The bottom layer is read through The contents of git folder are used to distinguish files, so it depends on the existence of git
Press w to display the menu and view the options of watch
In general, set o and F to use, first O (ignore the unchanged file, and it will be monitored when we change the file. Then press f repeatedly to only listen for the wrong use case)
jest report description
- Hover the mouse over the corresponding chart to display the corresponding prompt
- "5x" indicates that this statement has been executed 5 times in the test
- ć1ć If condition of test case is not entered, I.e. no test case with true if
ćEć This is the case when the test case does not test if condition is false
- That is, the if condition in the test case is always true. You have to write a test case whose if condition is false, that is, you don't enter the code in the if condition, and this E will disappear
Analog function , not a function of analog data
- It is only an analog Function (Function, jest.fn()), not a Function that generates analog data like mockjs
effect:
- Detect how many times the function has been executed
- Detect the point of this when the function is executed
- Input parameters during detection execution
- Return value after detection execution
Overlay simulation third-party function
- Override the Axios function, avoid actually initiating the interface, and customize the specific return value jest mock('axios'); axios. get. mockResolvedValue(resp);
- There is no magic or private adaptation, just a simple function overload. Equivalent to Axios Get = () = > resp overrides this method
The ultimate way to cover the entire third-party library
- Write the avatar file. When importing with import, the avatar file is imported
- You can also use jest Requireactual ('.. / foo bar Baz') to force the imported file to be a real file without using a avatar file
Timer simulation
- Copy the setTimeout timer. You can skip the specified time and shorten the running time of the unit test
Test snapshot
- Snapshot, that is, data copy, that is, to detect whether the "current data" is the same as the "old data copy", similar to JSON Stringify() to serialize and record data
Application scenario
- Restrict changes to configuration files
- Check the comparison of dom structure and whether the change of a function affects the dom structure
- Generally speaking, it is used for the comparison of big data to avoid writing the data in the unit test file
Other difficult and miscellaneous diseases
Alias and equivalent method
- it is the alias of test, and the two are equivalent
- toBeTruthy !== toBe(true),toBeFalsy !== toBe(false) and tobe (true) are more strict. Tobetrust is whether it is true after being strongly converted to boolean
- Skip skipping a test case is more elegant than annotation Skip ('test custom instruction ', xxx) ` test Custom command ('xxx ', skip test')`
jest toBe, internal use object Is for comparison
- The difference from = = = is that it behaves the same as the triple sign operator except NaN, + 0 and - 0
- Solve the calculation error of decimal point floating point number toBeCloseTo
Asynchronous test, passed resolves / . Rejections force the verification promise to take a specific branch
test('the data is peanut butter', () => { return expect(fetchData()).resolves.toBe('peanut butter'); });
Solve the overwrite problem with the default parameter of new Date
test('Current month, test parameters new Date Default value', () => { // Overwrite the value of new Date, simulated as 2022 / 01 / 23 17:13:03, to solve the problem that it cannot be overwritten when the default parameter is new Date const mockDate = new Date(1642938133000) const spyDate = jest .spyOn(global, 'Date') // That is, monitor global Date variable .mockImplementationOnce(() => { spyDate.mockRestore() // You need to eliminate the mock immediately after the first execution to avoid subsequent impact on subsequent new dates return mockDate }) let [starTime, endTime] = getMonthRange() expect(starTime).toBe(1640966400000) // 2022/01/01 00:00:00 expect(endTime).toBe(1643644799000) // 2022/01/31 23:59:59 })
Equivalent to using native syntax to write
const OriginDate = global.Date Date = jest.fn(() => { Date = OriginDate return new OriginDate(1642938133000) })
Use the latest syntax
beforeAll(() => { jest.useFakeTimers('modern') jest.setSystemTime(new Date(1466424490000)) // Because jest used in Vue Test Utils is version 24.9, there is no such function }) afterEach(() => { jest.restoreAllMocks() })
Match test, and run the same test case using the data of multiple batches
describe.each([ [1, 1, 2], // Each line represents a test case run [1, 2, 3], // The parameters in each line are the data used to run the test case. The first two are parameters and the third is the expected value of the test [2, 1, 3], ])( '.add(%i, %i)', // Set the title of describe,% i is the variable parameter of print (a, b, expected) => { test(`returns ${expected}`, () => { expect(a + b).toBe(expected); }); });
Gitlab CI unit test related configuration
- When a merge request is initiated, the ci is triggered to execute the unit test
When the unit test fails, execute the node file and send the flybook information. The flybook information includes the link of the merge request. You can click the link to quickly locate the unit test job and view the problem
stages: - merge-unit-test - merge-unit-test-fail-callback - other-test # job executed when merge is requested step-merge: stage: merge-unit-test # gitlab runner used tags: [front-end] # Execute only when a code merge request is made only: [merge_requests] # Exclude the code merge request of a specific branch, that is, the job will not be executed when the code merge request of a specific branch except: variables: - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "qa" # Command to run script: - npm install --registry=https://registry.npm.taobao.org # installation dependency # 2> & 1standard error directed to standard output # The Linux tee command is used to read the standard input data and output its contents to a file. - npm run test 2>&1 | tee ci-merge-unit-test.log # Execute the unit test and save the information output on the console in CI merge unit test Log file for subsequent analysis - echo 'merge-unit-test-finish' # Define the data to be transferred to the next job artifacts: when: on_failure # By default, it will only be saved in success and can be configured through this identifier paths: # Define the files to be transferred - ci-merge-unit-test.log # node command executed when merge detection fails step-merge-unit-test-fail-callback: stage: merge-unit-test-fail-callback # It is triggered only when the previous job fails to execute when: on_failure tags: [front-end] only: [merge_requests] except: variables: - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "qa" script: - node ci-merge-unit-test-fail-callback.js $CI_PROJECT_NAME $CI_JOB_ID # Execute the node script, notify the flying book, and carry the corresponding link for quick positioning
ci-merge-unit-test-fail-callback.js.js
const fs = require('fs') const path = require('path') const https = require('https') const projectName = process.argv[2] // Project name const jobsId = process.argv[3] // id of ci task executed const logsMainMsg = fs.readFileSync(path.join(__dirname, 'ci-merge-unit-test.log')) .toString() .split('\n') .filter(line => line[line.length - 1] !== '|' && line.indexOf('PASS ') !== 0) // Filter information you don't care about .join('\n') const data = JSON.stringify({ msg_type: 'post', content: { post: { zh_cn: { content: [ [ { tag: 'a', text: 'gitlab merge unit testing ', href: `https://xxx/fe-team/${projectName}/-/jobs/${Number(jobsId) - 1}` }, { tag: 'text', text: `Run failed\r\n${logsMainMsg}` } ] ] } } } }) const req = https.request({ hostname: 'open.feishu.cn', port: 443, path: '/open-apis/bot/v2/hook/xxx', method: 'POST', headers: { 'Content-Type': 'application/json' } }, res => { console.log(`statusCode: ${res.statusCode}`) res.on('data', d => process.stdout.write(d)) }) req.on('error', error => console.error(error)) req.write(data) req.end()
thank
- The output of recent articles is less, there are too many things, and I'm lazy
- Thank you for your concern and supervision. It feels good to be concerned