Front-end automated testing

Front-end automated testing

Front-end testing

The benefits of writing test code:

  • Find bugs faster, let the vast majority of bugs be found in the development stage, and improve product quality
  • Unit testing, by running the test code, observing the input and output, sometimes makes people understand your code better than comments.
  • It is conducive to code refactoring. If the test code of a project is well written, you can quickly check whether the refactoring is correct by checking whether the refactoring is correct by checking whether the refactoring is correct during the refactoring process and improving the efficiency of the refactoring.
  • The process of writing test code allows developers to think deeply about business processes and make code writing more complete and standardized.


  • Not all projects require front-end testing. Writing test code takes a certain amount of time. When the project is relatively simple, spending time writing test code may affect development efficiency.


TDD Test-Driven Development

Before developing the functional code, write the unit test case code first, and the test code determines what product code needs to be written.

Unit testing

When it comes to TDD, it generally refers to unit testing. It can also be called module testing. It refers to the inspection and verification of the smallest testable unit in the software. In the front end, the unit can be understood as an independent module file. Testing of such a module file.

BDD Behavior Driven Development

is to write business logic code first, and then write test code in order to make all business logic okay in accordance with the expected results. It is a development model that drives the development process by user behavior.

Integration Testing

Refers to the inspection and verification after all the modules in the software are assembled into a complete system according to the design requirements. At the front end, integration testing can be understood as testing a complete interactive process implemented by multiple modules.

For a system composed of multiple modules, the interactive behavior needs to be improved first, and then test code can be written according to the expected behavior.

So when it comes to BDD, it generally refers to integration testing.

Jest testing framework

Is a pleasant JavaScript testing framework.

Sample code

//Function code export function findMax ( arr ) { return Math .max(...arr) } //Test code test( 'findMax function output' , () => { expect(findMax([ 1 , 2 , 4 , 3 ])).toBe( 4 ) }) Copy code

1. Preparation

  1. initialization.

    npm init -y

  2. Install jest.

    npm install --sace-dev jest

  3. Generate jest configuration file.

    npx jest --init
    , Generate a jest configuration file

    • Test case coverage: Save the coverage report generated after each test is executed and need to be found
      Under the file
      Properties and set to
      . This will generate one after each execution of the test
      Folder, where coverage/lcov-report/index.html can display detailed test case coverage reports.
  4. Install Babel.

    npm i babel-jest @babel/core @babel/preset-env -D
    , Configuration
    as follows:

    { presets :[ [ '@babel/preset-env' , { targets :{ node : 'current' , } } ] ] } Copy code
  5. Create a directory of relevant test code files. Create a src folder in the root directory and create

    Entry file. Create a new project in the root directory of the project
    Catalog, create
    The file stores test cases.

  6. A new command is added to the package.json file:

    "scripts" : { "test" : "jest" , "coverage" : "jest --coverage" } Copy code

2. Basic use

2.1 Use process

Use TDD plus unit testing to use jest for testing:

Created in

Write the following code in the file:

function sum ( a, b ) { return a + b } function the sayHi ( E ) { return 'Hi' + E } const dessert = function ( name ) { this .name = name } dessert.prototype = { enjoy : function () { Return 'Enjoy The' + the this .name }, } module .exports = {sum, sayHi, dessert} Copy code

Write test cases:

let {sum, dessert, sayHi} = require ( '../index' ) describe( 'test sum' , () => { test( 'sum' , () => { expect(sum( 2 , 3 )).toBe( 5 ) }) }) describe( 'test dessert feature' , () => { test( 'enjoy the cake' , () => { const cake = new dessert( 'cake' ) expect(cake.enjoy()).toBe( 'Enjoy the cake' ) }) }) describe( 'test sayHi' , () => { test( 'hi' , () => { expect(sayHi( 'cch' )).toBe( 'hi cch!' ) }) }) Copy code
  • describe
    Combine test cases of the same type.
  • test
    Describe specific test cases, the smallest unit of unit testing.
  • expect
    Each expectation of the test result is compared with the expected value through the return value in the expect or the result of the function execution.
2.2 Coverage

npm coverage
Perform tests to generate test coverage.

![image-20210531164553506](/Users/a58/Library/Application Support/typora-user-images/image-20210531164553506.png)

It is statement coverage: Is every statement executed?

Branch coverage: Is every block of if code executed?

Function coverage: Is every function called?

Line coverage: Is every line executed?

2.3 Matcher
  • True and false related

    • toBe()
      Strictly equal, equivalent to
    • toEqual()
      Matchers, as long as the contents are equal.
    • toBeNull()
      The matcher only matches
      Value, cannot match
    • toBeUndifined()
      The matcher can match
    • toBeDefined()
      The matcher means that as long as it is defined, it can be matched successfully.
    • toBeTruthy() and toBeFalsy
  • Digital correlation

    • toBeLessThan()
      , When it is less than or greater than a number, it can pass the test.
    • toBeGreaterThanOrEqual()
      , Less than or equal to or greater than or equal to.
    • toBeCloseTo()
      , A matcher that eliminates JavaScript floating-point precision errors.
  • String matching related

    • toMatch()
      The matcher for string inclusion relations, checks whether the string matches the regular expression, or directly matches the string.
    • toContain()
      The matcher for the array.
    • toThrow()
      A matcher that handles exceptions can detect whether a method will throw an exception.
    • not
      The matcher is
      A special matcher in, which means
      in contrast

3. Frequently Asked Questions

3.1 Automatic test
  • Turn on automatic testing. Every time we modify the test case, we enter it manually
    yarn test
    , This seems very low. Can be configured
    File to set.
  • When writing unit tests, run at the same time
    Command, it will run automatically every time you save, check whether the current test statement is passed. Correct
    A brief introduction to several useful functions of the mode:
    1. press
      Key to run all test codes
    2. press
      Key just run all failed test code
    3. press
      Key to filter test codes according to file name (support regular)
    4. press
      Key to filter test code according to test name (support regular)
    5. press
      Keyboard launch
    6. press
      Key to trigger a test run
  • After writing a test file, you can run it
    Command, view the coverage of a branch or statement, or locate a folder to view the coverage of a module.
3.2 Asynchronous code testing
  • When receiving a callback function, it is impossible to determine whether the callback is completed. You need to add a done method as a parameter to ensure that the callback is completed.
Import Axios from 'Axios' export const fetchSuccessData = ( fn ) => { return axios.get( '' ).then( ( res ) => { fn( }) } Copy code
import {fetchSuccessData} from './fetchData.js' test( 'fetchSuccessData ' , ( done ) => { fetchSuccessData( ( data ) => { //console.log(data); expect(data.success).toBeTruthy() expect(data).toEqual({ success : true }) done() }) }) Copy code
  • Return one directly
    Asynchronous function of value
export const fetchDataPromise = () => { return axios.get( '' ) } test( 'fetchDataPromise ' , () => { return fetchDataPromise().then( ( res ) => { expect({ success : true }) }) }) test( 'fetchDataPromise' , () => { return expect(fetchDataPromise()).resolves.toMatchObject({ data :{ success : true } }) }) Copy code
  • Interface that does not exist. If you want to catch exceptions with catch, you need to use it in combination

    use. Because when using catch, this method will only be used when an exception occurs. If there is no exception, this test method will not be used, and Jest will default to this use case passing the test.
    Means "Assertion, the expect method must be executed once to pass the test".

    export const fetchData404 = () => { return axios.get( '' ) } test( 'fetchData404 ' , ()=> { expect.assertions( 1 ) //code must be executed once expect method return fetchData404().catch( ( e )=> { console .log(e.toString()) expect(e.toString().indexOf( '404' )>- 1 ).toBe( true ) }) }) test( 'fetchData404 ' , ()=> { return expect(fetchData404()).rejects.toThrow() }) Copy code
  • Use of async await

    test( 'fetchDataPromise ' , async () => { await expect(fetchDataPromise()).resolves.toMatchObject({ data :{ success : true } }) }) //You can also write test( 'fetchDataPromise ' , async () => { const res = await fetchDataPromise() expect( }) //When the return result is 404 test( 'fetchData404 async await' , async () => { expect.assertions( 2 ) try { await fetchData404() } catch (error) { console .log(error.toString()); expect(error.toString()).toEqual( 'Error: Request failed with status code 404' ) expect(error.toString().indexOf( '404' )>- 1 ).toBe( true ) } }) Copy code
3.3 Hook function and its scope in jest
  • beforeAll()
    The hook function means to execute before all test cases.
  • afterAll()
    A hook function is a function that is executed after all test cases are completed.
  • beforeEach()
    A hook function is a hook function that will be executed once before each test case.
  • afterEach()
    A hook function is a hook function that is executed once after each test case completes the test.
  • Hook functions are grouped at the parent level and can be scoped subsets, similar to inheritance
  • The scope of the hook function grouping at the same level does not interfere with each other, each works
  • Execute the external hook function first, and then execute the internal hook function
3.4 mock in jest
  • mock function Mock the function.
  • mock return value Mock the return value.
  • all
    Functions have a special
    Attribute, it saves information about how this function is called and the return value when it is called .
  • jest.fn()
    Perform some tests with callback functions, and directly simulate the return value of the function. To reimplement the simplified business logic in the function, change the corresponding call logic in the original function to the defined test data.
  • jest.mock()
    You can mock methods in the entire module. When a module has been 100% covered by unit tests, use
    Go to mock the module.
  • jest.fn()
    Is to create
    The simplest way to function, if the internal implementation of the function is not defined,
    Will return
    As the return value.
  • jest.fn()
    The created Mock function can also set the return value, define the internal implementation or return
    Object .
Related use
//mock.js export const run = fn => { return fn( 'this is run!' ) } //mock.test.js test( 'Test returns a fixed value' , () => { const func = jest.fn() func.mockReturnValue( 'this is mock fn1' ) func.mockReturnValueOnce( 'this is fn2' ).mockReturnValue( 'thi is fn3' ) const a = run(func) const b = run(func) const c = run(func) console .log(a) //this is mock fn2 console .log(b) //this is mock fn3 console .log(c) //this is mock fn1 }) test( 'Test jest.fn() initialization without passing parameters, change the content of the function through mockImplementation' , () => { const func = jest.fn() func.mockImplementation ( () => { return 'the this the mock Fn1 IS' }) func.mockImplementationOnce ( () => { return 'the mock Fn2 the this IS' }) const a = run(func) const b = run(func) const c = run(func) console .log(a) //this is mock fn2 console .log(b) //this is mock fn1 console .log(c ) //this is mock fn1 }) test( 'Test jest.fn() internal implementation' , () => { let mockFn = jest.fn( ( num1, num2 ) => { return num1 * num2; }) //Assert that mockFn returns 100 after execution expect(mockFn( 10 , 10 )).toBe( 100 ); }) Copy code
Related use

In front-end testing, you don t need to call the real back-end interface, you need to simulate

Module , so that it can test whether our interface call is correct without calling the api .

//fetch.js function forEach ( items, callback ) { for ( let index = 0 ; index <items.length; index++) { callback(items[index]); }; }; //events.js import fetch from './' ; export default { async getPostList () { return fetch.fetchPostsList( data => { console .log( 'fetchPostsList be called!' ); //do something }); } } //Test code import events from '../src / events ' ; import fetch from '../src/ fetch ' ; jest.mock( '../src/fetch.js' ); test( 'mock the entire fetch.js module' , async () => { expect.assertions( 2 ); await events.getPostList(); expect(fetch.fetchPostsList).toHaveBeenCalled(); expect(fetch.fetchPostsList).toHaveBeenCalledTimes( 1 ); }); Copy code
3.5 Testing of classes in ES6

If you only test the correct operation of the class's constructor and its methods, you can directly mock the class and its methods. This can avoid the problem of low execution efficiency when testing if the methods in the class are too complex.

//util.js class Util { init () { } fnA () { //console.log('fnA.'); } fnB () { //console.log('fnB'); } } export default Util //demo.js import Util from './util' const demoFunction = ( a, b ) => { const util = new Util() util.fnA(a) util.fnB(b) } export default demoFunction; //demoClass.test.js jest.mock( '../class / util.js' ) //The simulation util is executed first, and it is found that util is a class, and the constructor and method in the class are automatically replaced to jest.fn( ) //const util = jset.fn() //util.fnA = jest.fn() //util.fnB = jest.fn() //jest.fn() can be traced back to see if it is executed //jest.mock('../class/util.js', ()=> { //const util = jest.fn(()=>{ //console.log('constructor.'); //}) //Util.prototype.fnA = jest.fn(()=>{ //console.log('this is fn A ...'); //}) //}) import demoFunction from '../class/demo' import Util from '../class/ util ' test( 'Test demoFunction' , ()=> { demoFunction() //After calling this method, it means that the mock util is executed. expect(Util).toHaveBeenCalled(); //console.log(Util.mock.instances[0]); expect(Util.mock.instances[ 0 ].fnA).toHaveBeenCalled() expect(Util.mock.instances[ 0 ].fnB).toHaveBeenCalled() //expect(Util.mock.instances[0].init).toHaveBeenCalled()//demoFunction is not executed }) Copy code
3.6 Snapshot test

In our daily development, we will always write some configurable code, which will not change in general, but there will be minor changes. Such a configuration may be as follows:

//config.js export default getConfig = () => { return { "author" : "cch" , "name" : "v1" , "port" : 8000 , "server" : "http://localhost" , "time" : Any< Date >, } } //confog.test.js import {getConfig} from ' ./config ' test( 'getConfig test' , () => { expect(getConfig()).toEqual({ "author" : "cch" , "name" : "v1" , "port" : 8000 , "server" : "http://localhost" , "time" : Any< Date >, }) }) Copy code

Although the above writing can pass the test, if the configuration file is subsequently changed, the test code needs to be modified synchronously, which is very troublesome. So there is a snapshot test in jest


//confog.test.js import {getConfig} from ' ./config ' test( 'getConfig test' , () => { expect(getConfig()).toMatchSnapshot() }) Copy code

After running the test code, it will generate a

Folder, there is a
Snapshot files.

//snapshot.test.js.snap //Jest Snapshot v1, exports [ `getConfig test` ] = ` Object { "author": "cch", "name": "v1", "port": 8000, "server": "http://localhost", "time": Any<Date>, } ` ; Copy code

Will be running
, First check if there is this snapshot file, if not, then generate it, when we change the configuration content, such as
, Run the test code again, the test fails.

Can run at this time

yarn test - -u
Update the snapshot, or just press
Update the snapshot.

TDD and unit testing in Vue

1. Why test?

Unit testing of components has many benefits:

  • Provide documentation describing component behavior
  • Save time on manual testing
  • Reduce bugs generated when developing new features
  • Improved Design
  • Promote refactoring

Automated testing allows developers in large teams to maintain complex basic code.

2. Test Vue components directly with Jest

When importing a

When it is a component, it is just an object or function with a rendering function and some properties. To test the behavior of a component, you must first start it and start the rendering process. according to
The argument is that the component needs to be mounted.

To mount the component, you need to convert the component option to one

Constructor. The component options object is not a valid constructor, it is just an ordinary
Object. At this time you can use
Method to create one from the options
Constructor and use
Operator to create an instance.

Option to find the added rendered rendered in the document
node. But the general component constructor does not
Option, so when creating an instance, it will not automatically mount and generate
Node, need to be called manually

When calling

Will generate some
Node, you can use the instance
Properties access these nodes in the test:

Import Vue from 'VUE' ; Import Home from '@/views/Home.vue' ; describe( 'Home.vue' , () => { it( 'renders msg when mounted' , () => { const msg = 'Welcome to Your Vue.js App' ; //Use the Home option to create a new Vue constructor const Ctor = Vue.extend(Home); //Create a new Vue instance and mount it const vm = new Ctor().$mount(); //Access the DOM element and check the text content expect(vm.$el.textContent).toContain(msg) }) }) Copy code

3. Vue Test Utils

The official library for Vue component unit testing.

Vue Test Utils
Will export one
Method, after receiving a component, this method will mount it and return an instance containing the mounted component
The wrapper object. The reason why the wrapper object is returned instead of directly
Instance, because the wrapper is not just an instance
, Also includes some auxiliary methods. One of the methods is
, It returns the instance of

Import {} Mount from '@ VUE/Test-utils' ; Import Home from '@/views/Home.vue' ; describe( 'Home.vue' , () => { it( 'renders msg when mounted' , () => { const msg = 'Welcome to Your Vue.js App' ; //use the mount method to mount the component const wrapper = mount(Home); //check the text content expect(wrapper.text()).toContain(msg) }) }) Copy code
3.1 Frequently Asked Questions
  1. shallowMount

    Method will render the entire component tree,
    The method only renders one level of the component tree.
    Will not render the child components of the current component,
    The current component and sub-components will be rendered.


    Mount a component and return a wrapper. the difference lies in,
    Stub all sub-components before mounting the component.

    It can ensure that a component is tested independently, which helps to avoid messing up the results due to the rendering output of the factor component in the test.

    describe( 'Home.vue' , () => { it( 'renders msg when mounted' , () => { const msg = 'Welcome to Your Vue.js App' ; //use the shallowMount method to mount the component const wrapper = shallowMount(Home); //check the text content expect(wrapper.text()).toContain(msg) }) }) Copy code
  2. Test prop attribute

    Import {shallowMount} from '@ VUE/Test-utils' ; Import the Hello from '@/Components/HelloWorld.vue' ; describe( 'HelloWorld.vue' , () => { it( 'renders msg when mounted' , () => { const msg = 'hello, world' ; //Use the shallowMount method to mount the component const wrapper = shallowMount(Hello, { propsData : { msg } }); //Check the text content expect(wrapper.text()).toContain(msg) //Find the corresponding element directly and check the content in the element expect(wrapper.find( 'h1' ).text()).toContain(msg) expect(wrapper.props().msg).toBe(msg) }) }) Copy code
  3. Test DOM attributes in

    Vue Test Utils
    In the wrapper, there is a
    Method, you can return the component property object. You can use this object to test attribute values.

    describe( "About.vue" , () => { test( 'dom attribute test' , () => { const wrapper = mount(About) //console.log(wrapper.find('a').attributes()); expect(wrapper.find( 'a' ) .attributes().href).toEqual( "" ) expect(wrapper.find( 'a' ).attributes().href).toBe( "" ) }) }) Copy code
  4. Test the corresponding style in

    Vue Test Utils
    In the wrapper, there is a
    Method, which returns a
    Array. You can assert this to see if the element has a

    test( 'Test style' , () => { const wrapper = mount(About) //console.log(wrapper.vm) const target = wrapper.find( '#c2' ) console .log( 'View the class of the element ' , target.classes()); expect(target.classes()).toContain( 'c2' ) expect( 'red' ) expect(target.exists()).toBe( true ) }) Copy code

    The matcher can not only check whether one string contains another string, but also compare the values in the array.

    Every wrapper contains a

    Attribute, which is included on the wrapper
    Reference to the root node.

  5. Common methods:

    • attributes returns an object that wraps the DOM node attributes. If a key is provided, the value of the key is returned.

      import {mount} from '@vue/test-utils' import Foo from './Foo.vue' const wrapper = mount(Foo) expect(wrapper.attributes().id).toBe( 'foo' ) Expect (wrapper.attributes ( 'ID' )). TOBE ( 'foo' ) copying the code
    • classes returns the wrapper DOM node class. By default, an array of class names is returned. If a class name is provided, a Boolean value is returned.

      import {mount} from '@vue/test-utils' import Foo from './Foo.vue' const wrapper = mount(Foo) expect(wrapper.classes()).toContain( 'bar' ) Expect (wrapper.classes ( 'bar' )). TOBE ( to true ) copying the code
    • isVisible judgment

      Whether it s visible, it s mainly used to determine whether the component is

    • props return

      The attribute object. If provided
      , Then return

    • setData settings


    • trigger on

      The event is triggered asynchronously on the DOM node.
      Only valid for native DOM events.