1. Why front-end testing

First of all, I think the front end test is not all projects must be written test code needs to spend some time, when the project is simple, take the time to write the test code can affect the development efficiency, but need to point out that our front during the development process, writing test code, has the following benefits:

  1. Faster to find bugs, let the vast majority of bugs found in the development stage to solve, improve product quality
  2. Unit tests can be a better choice than writing comments, and running the test code and observing the inputs and outputs can sometimes make more sense of your code than comments (although important comments still need to be written…).
  3. It is conducive to reconstruction. If the test code of a project is written perfectly, it can quickly check whether the reconstruction is correct by testing whether the code passes during the reconstruction process, which greatly improves the reconstruction efficiency
  4. The process of writing test code often allows us to think deeply about business processes and make our code more complete and standardized.

2. What isTDDandBDD

2.1 TDDAnd unit testing

2.1.1 what isTDD

The so-called TDD(Test Driven Development), namely test-driven Development, is simply to write Test code first, and then in order to make all the Test code for the purpose of writing logic code, is a test-driven Development process Development mode.

2.1.2 Unit tests

Unit testing refers to the examination and verification of the smallest testable units in software. Generally speaking, in the front end, a unit can be understood as an independent module file, and a unit test is a test of such a module file.

For a standalone module (ES6 module), because the functionality is relatively independent, we can first write the test code, and then write the logical code according to the test code guidance.

So when it comes to TDD, testing generally refers to unit testing

2.2 BDDAnd integration testing

2.2.1 what isBDD

The so-called Behavior Driven Development (BDD) is the Behavior Driven Development. Simply speaking, it is to write the business logic code first, and then write the test code in order to make all the business logic execute according to the expected results. It is a Development mode that drives the Development process by the user’s Behavior.

2.2.2 Integration test

Integration Testing refers to the inspection and verification of all modules in the software after they have been assembled into a complete system according to design requirements. Colloquially speaking, at the front end, integration testing can be understood as testing a complete interactive process implemented by multiple modules.

For a system consisting of multiple modules (ES6 modules), the interaction behavior needs to be perfected first before the test code can be written according to the expected behavior.

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

3. JestUse — introduction section

3.1 How do we write test code?

If we had never worked with test code before, what would it be like to design the test code ourselves? We need to keep the test code simple and easy to understand. For example:

export function findMax (arr) {
    return Math.max(... arr) }Copy the code

We wrote a simple function that gets the maximum value of an array (you might think this is not rigorous, but for the sake of simplicity we’ll assume that the input is a non-empty array). If we wrote a test for this function, it might look something like this:

I expect findMax ([1.2.4.3]4
Copy the code

Further translated into English:

I expect findMax([1.2.4.3]) to be 4
Copy the code

In programmatic language, Expect, as a function, passes in the object it wants to test (findMax function) and encapsulates the output toBe(4):

expect(findMax([1.2.4.3])).toBe(4)  // There is an inside smell
Copy the code

Further, we want to add some descriptive information, such as

To test the findMax function, I expect findMax([1.2.4.3]4
Copy the code

The test function takes two arguments. The first argument is some descriptive information (in this case, the findMax function). The second argument is a function that can execute the above logic as follows:

test('findMax function output ', () => {
    expect(findMax([1.2.4.3])).toBe(4) // The inside taste is deeper
})
Copy the code

3.2 simple implementation of their own test code

We can simply implement the test and Expect functions ourselves. Since there is a chain call to toBe, expect should eventually return an object with a toBe method, as follows:

/ / the expect functions
function expect (value) {
    return {
        toBe: (toBeValue) = > {
            if (toBeValue === value) {
                console.log('Test passed! ')}else {
                throw new Error('Test failed! ')}}}}/ / the test function
function test (msg, func) {
    try {
        func()
        console.log(`${msg}No exception during the test! `)}catch (err) {
        console.error(`${msg}Error during test! `)}}Copy the code

Our test method is just a simple test of numbers. In actual projects, there are many types of tests. At this time, we can choose some mature test frameworks. A simple and powerful tool is right in front of us: JEST.

4. JestUse — Introductory section

4.1 Preparations

The purpose of this part of the example is to introduce the basic usage of Jest. First, we will set up a simple demo environment.

The first step is to initialize the project using NPM init -y(my Node version is V12.14.1, NPM version is v6.13.4)

Jest NPM install –save-dev jest

Third, run the NPX jest –init command to generate a jest configuration file jest.config.js. My options are as follows

Step 4 run NPM I Babel -jest @babel/ core@babel /preset-env -d to install Babel and set. Babelrc is as follows

{
  presets: [['@babel/preset-env',
      {
        targets: {
          node: 'current',},},],],};Copy the code

Step 5 create a SRC folder under the root directory and create two files basic.js and basic.test.js

Step 6: Add a command for package.json:

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

After completing the above six steps, our project structure should be as shown below

4.2 Basicjestusage

Next we use TDD plus unit testing to learn the basic usage of JEST:

First, define two utility functions in basic.js

// 1. Find the maximum value
export function findMax (arr) {}// 2. Given an integer array nums and a target value target, find the two integers in the array and the target values, return true if they exist, otherwise return false
export function twoSum (nums, target) {};Copy the code

Since it’s TDD, we’ll start by writing the test code, and in the process, we’ll learn the basic uses of the various Jest step-by-step. The test code is written in basic.test.js:

import { findMax, twoSum } from './basic'

// Expect findMax([2, 6, 3]) to result in 6
test('findMax([2, 6, 3])', () => {
    expect(findMax([2.6.3])).toBe(6)})// Expect twoSum([2, 3, 4, 6], 10) to be true
test('twoSum([2, 3, 4, 6], 10)', () => {
    expect(twoSum([2.3.4.6].10)).toBe(true)})Copy the code

NPM test: NPM test: NPM test: NPM test: NPM test: NPM test: NPM test:

Expected
toBe
Received
Received
undefined
1
Test Suites
2

Next we refine the logic in basic.js

// 1. Find the maximum value
export function findMax (arr) {
    return Math.max(... arr) }// 2. Given an integer array nums and a target value target, find the two integers in the array and the target values, return true if they exist, otherwise return false
export function twoSum (nums, target) {
    for (let i = 0; i < nums.length - 1; i++) {
       for (let j = i + 1; j < nums.length; j++) {
           if (nums[i] + nums[j] === target) {
               return true}}}return false
};
Copy the code

Then we run NPM test again and get the following result

TDD

4.3 morejest matchers

As in the previous section, the toBe that follows the Expect function to judge the result is called a matcher in Jest, and this section introduces some other commonly used matchers

4.3.1 toEqual

Let’s first modify our twoSum function to return an indexed array of the two numbers we found.

// 2. Given an array of integers (nums) and a target value (target), find the two integers in the array and the target values.
// And return their array subscripts (assuming there is only one answer for each input and that the same element in the array cannot be used twice).
export function twoSum (nums, target) {
    for (let i = 0; i < nums.length - 1; i++) {
       for (let j = i + 1; j < nums.length; j++) {
           if (nums[i] + nums[j] === target) {
               return [i, j]
           }
       } 
    }
    return[]}.Copy the code

For the next section of the test code, we’ll just keep testing the twoSum function and modify the test code simultaneously

test('twoSum([2, 3, 4, 6], 10)', () => {
    expect(twoSum([2.3.4.6].10)).toBe([2.3])})Copy the code

Our expectation is that the result of the function execution is an array like [2, 3], which looks fine. Run NPM test

We found that it didn’t pass the test, and that’s because toBe can judge primitives, but it can’t judge arrays, objects, and references, and that’s when we need toEqual

test('twoSum([2, 3, 4, 6], 10)', () => {
    expect(twoSum([2.3.4.6].10)).toEqual([2.3])})Copy the code

After changing to toEqual, the test code succeeds

4.3.2 Judge the truth and falsity of logicmatchers

This part of the content is very simple, and more, so directly in the code comment:

test('Is variable A null?', () = > {const a = null
    expect(a).toBeNull()
})

test('Is variable A undefined?', () = > {const a = undefined
    expect(a).toBeUndefined()
})

test('Is variable A defined?', () = > {const a = null
    expect(a).toBeDefined()
})

test('Is variable A true?', () = > {const a = 1
    expect(a).toBeTruthy()
})

test('Is variable A false?', () = > {const a = 0
    expect(a).toBeFalsy()
})
Copy the code

The test results are as follows:

4.3.3 notThe modifier

Quite simply, “not” is a rejection of Matcher

test('test not', () = > {const temp = 10
    expect(temp).not.toBe(11)
    expect(temp).not.toBeFalsy()
    expect(temp).toBeTruthy()
})
Copy the code

The test results are as follows:

4.3.4 Judge some relevant figuresmatchers

This part of the content is very simple, and more, so directly in the code comment:

// Check whether num is greater than a certain number
test('toBeGreaterThan', () = > {const num = 10
    expect(num).toBeGreaterThan(7)})// Check whether num is greater than or equal to a certain number
test('toBeGreaterThanOrEqual', () = > {const num = 10
    expect(num).toBeGreaterThanOrEqual(10)})// Check whether num is less than a certain number
test('toBeLessThan', () = > {const num = 10
    expect(num).toBeLessThan(20)})// Check whether num is less than or equal to a certain number
test('toBeLessThanOrEqual', () = > {const num = 10
    expect(num).toBeLessThanOrEqual(10)
    expect(num).toBeLessThanOrEqual(20)})Copy the code

The test results are as follows:

For example, we know that 0.1 + 0.2 = 0.3 is not a problem in mathematics, but in computer, due to precision problems, the result of 0.1 + 0.2 is not accurate 0.3 if the toBe method is used. If we want to determine the equality of floating-point numbers, matcher in Jest provides a toBeCloseTo solution:

test('toBe', () = > {const sum = 0.1 + 0.2
    expect(sum).toBe(0.3)
})

test('toBeCloseTo', () = > {const sum = 0.1 + 0.2
    expect(sum).toBeCloseTo(0.3)})Copy the code

The above test results are as follows:

4.3.5 String MatchingtoMatch

This matcher is used to determine if a string matches the pattern provided by toMatch, as follows:

// String correlation
test('toMatch', () = > {const str = 'Lebron James'
    expect(str).toMatch(/Ja/)
    expect(str).toMatch('Ja')})Copy the code

4.3.6 Arrays, collection relatedmatchers

ToHaveLength toHaveLength toHaveLength toHaveLength toHaveLength toHaveLength toHaveLength toHaveLength

test('Array Set matchers', () = > {const arr = ['Kobe'.'James'.'Curry']
    const set = new Set(arr)
    expect(arr).toContain('Kobe')
    expect(set).toContain('Curry')
    expect(arr).toHaveLength(3)})Copy the code

4.3.7 Exception relatedmatchers

Use toThrow to determine if an exception is thrown as expected:

function throwError () {
    throw new Error('this is an error!! ')
}
test('toThrow', () => {
    expect(throwError).toThrow(/this is an error/)})Copy the code

5. jestAdvanced usage

5.1 Grouping tests and hook sub functions

The core of the so-called group test is to group different tests and then combine the hook sub-function (life cycle function) to complete the customized test of different groups, so as to meet the complex requirements of the test process.

We first create two new files under SRC, hook. Js and hook. Test. js

// hook.js
export default class Count {
    constructor () {
        this.count = 2
    }
    increase () {
        this.count ++
    }

    decrease () {
        this.count --
    }

    double () {
        this.count *= this.count
    }

    half () {
        this.count /= this.count
    }
} 
Copy the code

Now, we want to test the four methods of the Count class separately, and the data does not affect each other. Of course, we can directly instantiate the four objects ourselves. However, Jest gives us a more elegant way to write groups.

describe('Four separate methods to test Count', () => {
    test('test happens', () => {
        
    })
    test('test decrease', () => {
        
    })
    test('test double', () => {
        
    })
    test('test half', () => {})})Copy the code

We used the describe function and test to split the tests into four groups. Next, to better control each group, we used jEST’s ticker function. We here is to introduce the four hook function in jest beforeEach, beforeAll, afterEach, afterAll.

As the name implies, beforeEach is called beforeEach test function is executed; AfterEach is called afterEach test function. BeforeAll is called beforeAll test functions are executed; AfterAll is called afterAll test functions have been executed. We can look at this example:

import Count from "./hook"

describe('Four separate methods to test Count', () = > {let count
    beforeAll((a)= > {
        console.log('before all tests! ')
    })

    beforeEach((a)= > {
        console.log('before each test! ')
        count = new Count()
    })

    afterAll((a)= > {
        console.log('after all tests! ')
    })

    afterEach((a)= > {
        console.log('after each test! ')
    })

    test('test happens', () => {
        count.increase()
        console.log(count.count)
    })
    test('test decrease', () => {
        count.decrease()
        console.log(count.count)
    })
    test('test double', () => {
        count.double()
        console.log(count.count)
    })
    test('test half', () => {
        count.half()
        console.log(count.count)
    })
})
Copy the code

The output is as follows:

As you can see, we re-instantiate count in beforeEach test execution, so the count is different each time. With proper use of tick sub functions, we can better customize the test.

5.2 Timer for asynchronous code test

In our front-end development process, because javascript is a single thread, asynchronous programming is the work we developers often have to do, and asynchronous code is also the most prone to error, it is necessary to test asynchronous code logic, this section will be how jEST asynchronous test, do a detailed introduction.

5.2.1 From the simplestsetTimeoutstart

Js, timeout.test.js file. The code of the timeout.js file is very simple:

export default (fn) => {
    setTimeout((a)= > {
       fn()
    }, 2000)}Copy the code

Our goal now is to test whether the function we wrote will pass in a function as an argument (simple, no parameter verification) as we expected, and then execute the function 2 seconds later.

Our test code (timeout.test.js) looks like this:

import timeout from './timeout'

test('test timer', () => {
    timeout((a)= > {
        expect(2+2).toBe(4)})})Copy the code

If we run the test code, it will pass, but does it really mean that the test we wrote in timeout passed? We print out a text in timout.js

export default (fn) => {
    setTimeout((a)= > {
       fn()
       console.log('this is timeout! ')},2000)}Copy the code

Then we run the test code (NPM test timeout.test runs only one file) and you will find that nothing is printed:

jest
test
Do not wait for the results of asynchronous code execution
setTimeout

If we want the test code to return the test result only after it actually executes the asynchronous logic in the timer, we need to pass a done argument to the test callback and call the done method in the asynchronously executed code in the test method. The test method does not return the result of the test until the content of the code block in which done is executed:

import timeout from './timeout'

test('test timer', (done) => {
    timeout((a)= > {
        expect(2+2).toBe(4)
        done()
    })
})
Copy the code

done

5.2.2 usefakeTimersImprove test efficiency

Section introduces how to test we write in the timer asynchronous code execution, but there is a problem, for example, we might need a few seconds to perform internal timer logic (although this is very rare, the main business requirements), we test code will be a long time will return a result, this undoubtedly greatly reduce the development testing efficiency.

Jest also takes this into account, allowing us to use fakeTimers to simulate real timers. The fakeTimers allow us to immediately skip the timer wait time and execute internal logic when we encounter a timer. For example, for timeout.test, our test code can change as follows:

  1. First, we usejest.fn()To generate ajestProvides a function to test, so that we don’t need to write our own callback function
  2. Secondly, we usejest.useFakeTimers()Methods to start thefakeTimers
  3. Finally, we can passjest.advanceTimersByTime()Method, the parameter is passed in millisecond time,jestWill immediately skip this time value, can also passtoHaveBeenCalledTimes()thismathcerTo test the number of times the function is called.

The complete code is as follows:

test('test timer', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timeout(fn)
    // Fast forward 2 seconds
    jest.advanceTimersByTime(2000)
    expect(fn).toHaveBeenCalledTimes(1)})Copy the code

Test the timer (ms)
The test timer (2021 ms)

5.2.3 More complex timer scenarios

After the introduction of the previous two sections, we have actually mastered the core content of writing test code for timer asynchronous scenarios. In this section, we will explore a more complex scenario, that is timer nesting.

We will first modify the code in Timout as follows:

export default (fn) => {
    setTimeout((a)= > {
       fn()
       console.log('this is timeout outside! ')
       setTimeout((a)= > {
            fn()
           console.log('this is timeout inside! ')},3000)},2000)}Copy the code

As written in the previous section, our test code can be modified to read:

test('test timer', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timeout(fn)
    // Fast forward 2 seconds
    jest.advanceTimersByTime(2000)
    expect(fn).toHaveBeenCalledTimes(1)
    // Fast forward 3 seconds
    jest.advanceTimersByTime(3000)
    expect(fn).toHaveBeenCalledTimes(2)})Copy the code

Well, it’s pretty simple, just execute the second timer after the first 2s and then after the second 3s, and then fn gets called twice, so we just need to add the last two lines of code. The result is as follows:

We can see that both prints are printed. But the current implementation is not very good, just think, if there are more nested timers, or we do not know how many timers, it will be more troublesome. Jest provides two useful approaches to this situation:

  1. jest.runAllTimers()

This method, as its name suggests, executes all timers. Our code can be modified as follows:

test('test timer', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timeout(fn)
    jest.runAllTimers()
    expect(fn).toHaveBeenCalledTimes(2)})Copy the code

jest

  1. jest.runOnlyPendingTimers()

In this example, only the outer timer is waiting. The inner timer is waiting only when the outer timer is executing. We modify the test code as follows:

test('test timer', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timeout(fn)
    jest.runOnlyPendingTimers()
    expect(fn).toHaveBeenCalledTimes(1)})Copy the code

jest.runOnlyPendingTimers()

One thing to note about the above:

If we write multiple test functions that useFakeTimers, be sure to call jest. UseFakeTimers () every time in the beforeEach hook, otherwise fakeTimers in multiple test functions will be the same and will interfere with each other. An execution result that does not meet expectations is generated

beforeEach((a)= > {
    jest.useFakeTimers()
})
Copy the code

5.3 Data requests for asynchronous code tests(promise/async await)

5.3.1 traditionalpromisewriting

In our front-end development, it is a very important process to obtain data through the request back-end interface. This section mainly introduces how to write test code during this process (in fact, much of the content here has been introduced in the previous chapter on timer).

For simplicity, we use axiOS (NPM I AXIos), a mature library to assist us in making data requests. Create request.js, request.test.js, and request a free API in request.js:

import axios from 'axios'

export const request = fn= > {
    axios.get('https://jsonplaceholder.typicode.com/todos/1').then(res= > {
        fn(res)
        console.log(res)
    })
}
Copy the code

In request.test.js, we pass the done argument to the test callback and execute done() in the callback function as described above, in order to ensure that the asynchronous code finishes the test:

import { request } from './request'

test('test request', (done) => {
    request(data= > {
        expect(data.data).toEqual({
            "userId": 1."id": 1."title": "delectus aut autem"."completed": false
          })
        done()
    })
})
Copy the code

We’ll now modify the request.js code to return a promise:

export const request = (a)= > {
    return axios.get('https://jsonplaceholder.typicode.com/todos/1')}Copy the code

To test the above code, we also need to make some changes to request.test.js:

test('test request', () = > {return request().then(data= > {
        expect(data.data).toEqual({
            "userId": 1."id": 1."title": "delectus aut autem"."completed": false})})})Copy the code

If jest executes test without a return, it will not wait for the promise to return. If jest executes test without a return, the then method will not execute when the test results are output. We could try one of two approaches (changing “completed”: true). The first one failed and the second one passed (because the promise did not return a result) :

/ / the first
test('test request', () = > {return request().then(data= > {
        expect(data.data).toEqual({
            "userId": 1."id": 1."title": "delectus aut autem"."completed": true})})})Copy the code

/ / the second
test('test request', () => {
    request().then(data= > {
        expect(data.data).toEqual({
            "userId": 1."id": 1."title": "delectus aut autem"."completed": true})})})Copy the code

We could also write the above test code as follows:

test('test request', () = > {return expect(request()).resolves.toMatchObject({
        data: {
            "userId": 1."id": 1."title": "delectus aut autem"."completed": false}})})Copy the code

At this point, we use the Matcher toMatchObject. The test will pass when the incoming object matches some of the key/value pairs that the Request method returns.

5.3.2 useasync awaitSyntactic sugar

Async await is essentially the syntactic candy of a promise chain call. The code we tested at the end of the previous section, if written with async await, is as follows:

/ / write one
test('test request'.async() = > {const res = await request()
    expect(res.data).toEqual({
        "userId": 1."id": 1."title": "delectus aut autem"."completed": false})})/ / write two
test('test request'.async() = > {await expect(request()).resolves.toMatchObject({
        data: {
            "userId": 1."id": 1."title": "delectus aut autem"."completed": false}})})Copy the code

5.3.3 Tests for request errors

In our real project, we would need to do error handling for such interface requests, and we would also need to write test code for exceptions.

We’ll start by adding a method to request.js:

export const requestErr = fn= > {
    return axios.get('https://jsonplaceholder.typicode.com/sda')}Copy the code

Request an interface address that does not exist, return 404, so our test code is:

test('Test Request 404', () = > {return requestErr().catch((e) = > {
        console.log(e.toString())
        expect(e.toString().indexOf('404') > - 1).toBeTruthy()
    })
})
Copy the code

jest

catch
catch
expect.assertions(1)
catch
catch

This is no longer needed, but is detailed in the examples above, so just write as above and test through to indicate that this is exactly what we expect.

Similarly, there is another way to do exception code testing:

test('Test Request 404', () = > {return expect(requestErr()).rejects.toThrow(/ 404 /)})Copy the code

This rejects and the convergent match from the previous section represent the convergent error object generated by the execution method that throws a 404 exception (toThrow(/404/))

We can also use the async await syntax sugar to write exception test code:

test('Test Request 404'.async() = > {await expect(requestErr()).rejects.toThrow(/ 404 /)})// Or you can use a try catch statement to write more completely
test('Test Request 404'.async() = > {try {
        await requestErr()
    } catch (e) {
        expect(e.toString()).toBe('Error: Request failed with status code 404')}})Copy the code

5.4 Simulating in tests (mock) data

We’ll start by creating a new mock.js, mock.test.js file

5.4.1 usejest.fn()Simulation function

Start by writing a function in mock.js:

export const run = fn= > {
   return fn('this is run! ')}Copy the code

We’ve actually used jest.fn() before, but let’s take it a step further.

  1. First of all, ourfn()A function can take a function as an argument, and that function is what we wantjest.fn()For usmockFunction of, we writemock.test.js:
test('test jest. Fn ()', () = > {const fn = jest.fn((a)= > {
        return 'this is mock fn 1'})})Copy the code
  1. Second,jest.fn()Can be initialized without passing in arguments and then generated by callingmockFunction of themockImplementationormockImplementationOnceMethod to change the contents of the mock function. The difference between the two methods is thatmockImplementationOnceIt just changes what you wantmockFunction once:
test('test jest. Fn ()', () = > {const func = jest.fn()
    func.mockImplementation((a)= > {
        return 'this is mock fn 1'
    })
    func.mockImplementationOnce((a)= > {
        return 'this is mock fn 2'
    })
    const a = run(func)
    const b = run(func)
    const c = run(func)
    console.log(a)
    console.log(b)
    console.log(c)
})
Copy the code

this is mock fn 2
this is mock fn 1

Similarly, we can change the return value of a mock function using the mockReturnValue and mockReturnValueOnce (once) methods:

test('test jest. Fn ()', () = > {const func = jest.fn()
    func.mockImplementation((a)= > {
        return 'this is mock fn 1'
    })
    func.mockImplementationOnce((a)= > {
        return 'this is mock fn 2'
    })
    func.mockReturnValue('this is mock fn 3')
    func.mockReturnValueOnce('this is mock fn 4')
        .mockReturnValueOnce('this is mock fn 5')
        .mockReturnValueOnce('this is mock fn 6')
    const a = run(func)
    const b = run(func)
    const c = run(func)
    const d = run(func)
    console.log(a)
    console.log(b)
    console.log(c)
    console.log(d)
})
Copy the code

Note that methods can be called chained, making it easy to print different return values multiple times.

  1. Finally, we can usetoBeCalledWiththismatcherTo test whether the function passes the parameters as expected:
test('test jest. Fn ()', () = > {const func = jest.fn()
    const a = run(func)
    expect(func).toBeCalledWith('this is run! ')})Copy the code

5.4.2 Simulating the data obtained in the interface

Many times during front-end development, we need to mock the data returned by the back-end interface that has not yet been provided.

We’ll start by writing a simple request for data in mock.js:

import axios from 'axios'

export const request = fn= > {
    return axios.get('https://jsonplaceholder.typicode.com/todos/1')}Copy the code

Next, in mock.test.js, we mock axios using the jest. Mock () method, mock the returned data using the mockResolvedValue and mockResolvedValueOnce methods, and again, The mockResolvedValueOnce method changes the returned data only once:

import axios from 'axios'
import { request } from './mock'

jest.mock('axios')

test('test request'.async () => {
    axios.get.mockResolvedValueOnce({ data: 'Jordan'.position: 'SG' })
    axios.get.mockResolvedValue({ data: 'kobe'.position: 'SG' })
    await request().then((res) = > {
        expect(res.data).toBe('Jordan')})await request().then((res) = > {
        expect(res.data).toBe('kobe')})})Copy the code

Mock (‘axios’) to use Jest to emulate Axios to test that it passes correctly.

5.5 domRelevant test

Create dom.js and dom.test.js files as follows:

// dom.js
export const generateDiv = (a)= > {
    const div = document.createElement('div')
    div.className = 'test-div'
    document.body.appendChild(div)
}

// dom.test.js
import { generateDiv } from './dom'

test('Test DOM manipulation', () => {
    generateDiv()
    generateDiv()
    generateDiv()
    expect(document.getElementsByClassName('test-div').length).toBe(3)})Copy the code

The only caveat here is that Jest runs in Node.js, and jest uses jsDOM to allow us to write test logic for DOM manipulation.

5.6 the snapshot (snapshot) test

If we hadn’t been exposed to snapshot testing, that might sound like a fancy name. So let’s first create snapshot.js, shapshot.test.js to see what snapshot tests really are.

In our daily development, we will always write some configuration code, which will not change in general, but will also have minor changes, such configuration may be as follows (snapshot.js) :

export const getConfig = (a)= > {
    return {
        server: 'https://demo.com'.port: '8080'}}Copy the code

Our test code is as follows:

import { getConfig } from './snapshot'

test('getConfig test', () => {
    expect(getConfig()).toEqual({
        server: 'https://demo.com'.port: '8080'})})Copy the code

So we passed the test. However, if our configuration changes later, I need to modify the test code synchronously, which will be troublesome, so jEST introduces the snapshot test for us, first test code:

test('getConfig test', () => {
    expect(getConfig()).toMatchSnapshot()
})
Copy the code

After we run the test code, we will generate a __snapshots__ folder in the project root directory with a snapshot.test.js.snap snapshot file, which contains the following contents:

// Jest Snapshot v1, https://goo.gl/fbAQLP exports[' getConfig test 1 '] = 'Object {"port": "8080"."server": "https://demo.com",} `;Copy the code

When toMatchSnapshot() is run, jest will check whether the snapshot file exists. If it does not exist, jest will generate the snapshot file. When we change the configuration, such as changing port to 8090, jest will run the test code again.

npm test snapshot.test -- -u

At this point our snapshot file is updated with the following code:

// Jest Snapshot v1, https://goo.gl/fbAQLP exports[' getConfig test 1 '] = 'Object {"port": "8090"."server": "https://demo.com",} `;Copy the code

6. jestSome other useful things

6.1 letjestListening for file changes

This function is very simple, we just need to run the jest command, followed by –watch, we add a new command in package.json:

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

After adding this command, we also need to make our code files into a Git repository in order for Jest to listen for file changes. Jest also relies on git’s ability to listen for file changes. We run git init, then run NPM run test-watch. The last few lines of output from the command line should read:

Here are a few useful features of Watch mode:

  1. According to theaKey to run all the test code
  2. According to thefKey only runs all failed test code
  3. According to thepKey filters test code by filename (re support)
  4. According to thetKey filters test code by test name (re support)
  5. According to theqThe keyboard to launchwatchmodel
  6. According to theenterKey triggers a test run

I encourage you to try these out on your own, they are very simple and easy to use features.

6.2 Generating test coverage Files

Jest provides us with a method to directly generate the test coverage file, that is, to run the jest command followed by the –coverage parameter. We modify the package.json file as follows:

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

Next, run NPM run coverage and we can see the command line output as follows:

coverage

index.html

Here’s a brief description of the table project:

  1. Statements are statement coverage: Statements indicate how many Statements executed in the code are tested

  2. Branches Refers to the number of if else switch Branches tested in the code

  3. Functions are function coverage: Represents the percentage of Functions in your code that are tested

  4. Lines is line coverage: represents the percentage of Lines tested in your code

We can use the generated test coverage files to better improve our test code.

6.3 aboutjest.config.jsThe configuration file

My advice for learning a tool’s configuration file is to stick to the default file first, and then consult the official documentation when you need to change the configuration. It is not recommended to memorize the configuration file.

I won’t explain how to configure the jest file. You can use the jest.config.js generated by default when jest is initialized (there are detailed comments), or you can check the related configuration parameters in the official website.

7. Write at the end

Due to the length, it is not suitable to introduce more information. For more INFORMATION related to API, it is recommended to refer to the official website for learning.

In my opinion, this article has explained the basic and core content of JEST. Maybe we use development frameworks such as React (Enzyme), VUE (@vue/test-utils) and engineering tools such as Webpack during the development of JEST. I’ll use some open source libraries in combination, and I’m sure that once I’ve learned jEST itself, it won’t be too difficult to configure and use them.

Finally, I hope this article can help everyone, thanks to see here every friend.