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.

Disadvantages:

  • 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 and BDD

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
    jest.config.js
    .

    • Test case coverage: Save the coverage report generated after each test is executed and need to be found
      jest.config.js
      Under the file
      collectCoverage
      Properties and set to
      true
      . This will generate one after each execution of the test
      coverage
      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
    .babelrc
    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

    index.js
    Entry file. Create a new project in the root directory of the project
    __tests__
    Catalog, create
    index.test.js
    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

index.js
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)

%Stmts
It is statement coverage: Is every statement executed?

%Branch
Branch coverage: Is every block of if code executed?

%Funcs
Function coverage: Is every function called?

%Lines
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
      null
      Value, cannot match
      undefined
      Value.
    • toBeUndifined()
      The matcher can match
      undefined
      value.
    • toBeDefined()
      The matcher means that as long as it is defined, it can be matched successfully.
    • toBeTruthy() and toBeFalsy
      ,Yes
      true
      with
      false
      Matcher.
  • Digital correlation

    • toBeLessThan()
      versus
      toBeGreaterThan()
      , When it is less than or greater than a number, it can pass the test.
    • toBeGreaterThanOrEqual()
      with
      toBeLessThanOrEqual()
      , 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
      not
      The matcher is
      Jest
      A special matcher in, which means
      in contrast
      Or
      Negate.

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
    package.json
    File to set.
  • When writing unit tests, run at the same time
    --watch
    Command, it will run automatically every time you save, check whether the current test statement is passed. Correct
    watch
    A brief introduction to several useful functions of the mode:
    1. press
      a
      Key to run all test codes
    2. press
      f
      Key just run all failed test code
    3. press
      p
      Key to filter test codes according to file name (support regular)
    4. press
      t
      Key to filter test code according to test name (support regular)
    5. press
      q
      Keyboard launch
      watch
      mode
    6. press
      enter
      Key to trigger a test run
  • After writing a test file, you can run it
    --coverage
    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( 'https://fe-mock.renrenaiche.cn/mock/60adc8397af972436c4c62bf/IsSuccess' ).then( ( res ) => { fn(res.data) }) } 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
    promise
    Asynchronous function of value
export const fetchDataPromise = () => { return axios.get( 'https://fe-mock.renrenaiche.cn/mock/60adc8397af972436c4c62bf/IsSuccess' ) } test( 'fetchDataPromise ' , () => { return fetchDataPromise().then( ( res ) => { expect(res.data).toEqual({ 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

    expect.assertions(1)
    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.
    expect.assertions(1)
    Means "Assertion, the expect method must be executed once to pass the test".

    export const fetchData404 = () => { return axios.get( 'https://fe-mock.renrenaiche.cn/mock/60adc8397af972436c4c62bf/IsSuccess111' ) } 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(res.data.success).toBeTruthy }) //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
    mock
    Functions have a special
    .mock
    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
    jest.mock()
    Go to mock the module.
  • jest.fn()
    Is to create
    Mock
    The simplest way to function, if the internal implementation of the function is not defined,
    jest.fn()
    Will return
    undefined
    As the return value.
  • jest.fn()
    The created Mock function can also set the return value, define the internal implementation or return
    Promise
    Object .
jest.fn()
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
jest.mock()
Related use

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

axios/fetch
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

toMatchSnapshot()
.

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

After running the test code, it will generate a

__snapshots__
Folder, there is a
snapshot.test.js.snap
Snapshot files.

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

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

Can run at this time

yarn test - -u
Update the snapshot, or just press
u
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

Vue
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
Vue
The argument is that the component needs to be mounted.

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

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

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

When calling

$mount
Time,
Vue
Will generate some
DOM
Node, you can use the instance
$el
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
mount
Method, after receiving a component, this method will mount it and return an instance containing the mounted component
vm
The wrapper object. The reason why the wrapper object is returned instead of directly
vm
Instance, because the wrapper is not just an instance
vm
, Also includes some auxiliary methods. One of the methods is
text
, It returns the instance of
textContent
.

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
    with
    mount
    method

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

    versus

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

    shallowMount
    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
    attribute
    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( "http://www.baidu.com" ) expect(wrapper.find( 'a' ).attributes().href).toBe( "http://www.baidu.com" ) }) }) Copy code
  4. Test the corresponding style in

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

    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(target.element.style.color).toBe( 'red' ) expect(target.exists()).toBe( true ) }) Copy code

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

    Every wrapper contains a

    element
    Attribute, which is included on the wrapper
    DOM
    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

      Wrapper
      Whether it s visible, it s mainly used to determine whether the component is
      v-show
      Hidden

    • props return

      Wrapper
      vm
      The attribute object. If provided
      key
      , Then return
      key
      value.

    • setData settings

      Wrapper
      vm
      data.

    • trigger on

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