In the last article, we covered the basics of using Jest. In this article, we will talk about how to use Jest in depth

There were a lot of problems in testing, like how to test asynchronous logic, how to mock interface data…

Through this article, you can develop the application of Jest with ease, let’s clear up the various doubts!

1.Jest advanced use

1.1 Test of asynchronous functions

There are only two ways to talk about asynchrony, the callback method and the now-popular promise method

export const getDataThroughCallback = fn= > {
  setTimeout((a)= > {
    fn({
      name: "webyouxuan"
    });
  }, 1000);
};

export const getDataThroughPromise = (a)= > {
  return new Promise((resolve, reject) = > {
    setTimeout((a)= > {
      resolve({
        name: "webyouxuan"
      });
    }, 1000);
  });
};
Copy the code

Let’s write the async.test.js method

import {getDataThroughCallback,getDataThroughPromise} from './3.getData';

// The default test case does not wait for the test to complete, so add the done argument and call the done function when it is done
it('Test incoming callback function for asynchronous return result',(done)=>{ // Asynchronous test methods can be done
    getDataThroughCallback((data) = >{
        expect(data).toEqual({
          name:'webyouxuan'}); done(); })})// Returning a promise waits for the promise to complete
it('Test promise returns result 1', () = > {return getDataThroughPromise().then(data= >{
        expect(data).toEqual({
          name:'webyouxuan'}); })})// Use async + await syntax directly
it('Test promise returns result 2'.async() = > {let data = await getDataThroughPromise();
    expect(data).toEqual({
      name:'webyouxuan'
    });
})
// Use a built-in matcher
it('Test promise returns result 3'.async ()=>{
    expect(getDataThroughPromise()).resolves.toMatchObject({
      name:'webyouxuan'})})Copy the code

2. The mock in Jest

2.1 Simulation function jest. Fn ()

Why simulate a function? If you look at the following scenario, how would you test it

export const myMap = (arr,fn) = >{
   return arr.map(fn)
}
Copy the code

It’s easy to see, we just need to determine what the function returns, like this

import { myMap } from "./map";
it("Test map method", () = > {let fn = item= > item * 2;
  expect(myMap([1.2.3], fn)).toEqual([2.4.6]);
});
Copy the code

But we want to be more detailed, like each call to the function is passed each item of the array, whether the function is called three times, to be more specific is to trace the specific execution of the function!

import { myMap } from "./map";
it("Test map method", () = > {// Functions declared by jest. Fn can be traced
  let fn = jest.fn(item= > (item *= 2));
  expect(myMap([1.2.3], fn)).toEqual([2.4.6]);
  // call three times
  expect(fn.mock.calls.length).toBe(3);
  // each time the function returns the values 2,4,6
  expect(fn.mock.results.map(item= >item.value)).toEqual([2.4.6])});Copy the code

What does this mock look like in detail

2.2 Mock file jest. Mock ()

If we want to mock the interface, we can create a file with the same name in the __mocks__ directory and mock out the entire file, for example, the current file is api.js

import axios from "axios";

export const fetchUser = (a)= >{
    return axios.get('/user')}export const fetchList = (a)= >{
    return axios.get('/list')}Copy the code

Create __mocks__ / API. Js

export const fetchUser = (a)= >{
    return new Promise((resolve,reject) = > resolve({user:'webyouxuan'}}))export const fetchList = (a)= >{
    return new Promise((resolve,reject) = >resolve(['banana'.'apple'))}Copy the code

To begin testing

jest.mock('./api.js'); // Use api.js under __mocks__
import {fetchList,fetchUser} from './api'; // Introduce mock methods
it('fetchUser test'.async() = > {let data = await fetchUser();
    expect(data).toEqual({user:'webyouxuan'})
})

it('fetchList test'.async() = > {let data = await fetchList();
    expect(data).toEqual(['banana'.'apple'])})Copy the code

Jest. RequireActual (‘./api.js’) is used to introduce the real file if the mock api.js methods are incomplete and you may need to introduce the original file methods during testing.

Here we need to consider whether it’s a bit of a hassle to mock out the actual request, so can we mock the axios method directly?

Create axios.js under __mocks__ and override the get method

export default {
    get(url){
        return new Promise((resolve,reject) = >{
            if(url === '/user'){
                resolve({user:'webyouxuan'});
            }else if(url === '/list'){
                resolve(['banana'.'apple']); }}}})Copy the code

__mocks__/axios.js is looked for by default when axios is called in a method

jest.mock('axios'); // Mock axios method
import {fetchList,fetchUser} from './api';
it('fetchUser test'.async() = > {let data = await fetchUser();
    expect(data).toEqual({user:'webyouxuan'})
})

it('fetchList test'.async() = > {let data = await fetchList();
    expect(data).toEqual(['banana'.'apple'])})Copy the code

2.3 to simulate the Timer

In this case, we expect a callback and want to see if the callback can be called

export const timer = callback= >{
    setTimeout((a)= >{
        callback();
    },2000)}Copy the code

So it’s easy to write test cases like this

import {timer} from './timer';
it('Will callback execute?',(done)=>{
    let fn = jest.fn();
    timer(fn);
    setTimeout((a)= >{
        expect(fn).toHaveBeenCalled();
        done();
    },2500)});Copy the code

Do you feel stupid about it? What if it takes a long time? How many timers? That’s where the mock Timer comes in

import {timer} from './timer';
jest.useFakeTimers();
it('Will callback execute?', () = > {let fn = jest.fn();
    timer(fn);
    // Run all timers, what if the code to be tested is a stopwatch?
    // jest.runAllTimers();
    // Move the time back 2.5s
    // jest.advanceTimersByTime(2500);

    // Run only the current wait timer
    jest.runOnlyPendingTimers();
    expect(fn).toHaveBeenCalled();
});
Copy the code

3. Hook functions in Jest

For testing convenience, Jest also provides vUe-like hook functions that can be executed before or after test cases

class Counter {
  constructor() {
    this.count = 0;
  }
  add(count) {
    this.count += count; }}module.exports = Counter;
Copy the code

We write the test case to test whether the Add method in the Counter class works as expected

import Counter from './hook'
it('Test counter adds 1 function', () = > {let counter = new Counter; // Each test case needs to create a counter instance to prevent interaction
    counter.add(1);
    expect(counter.count).toBe(1)
})

it('Test Counter adds 2 features', () = > {let counter = new Counter;
    counter.add(2);
    expect(counter.count).toBe(2)})Copy the code

We found that each test case needed to be tested against a new counter instance to prevent interaction between test cases, so we could put the repeated logic in the hooks!

Hook function

  • BeforeAll executes beforeAll test cases are executed
  • AfteraAll after all test cases are executed
  • BeforeEach before execution of each use case
  • AfterEach is executed afterEach use case
import Counter from "./hook";
let counter = null;
beforeAll((a)= >{
    console.log('before all')}); afterAll((a)= >{
    console.log('after all')}); beforeEach((a)= > {
  console.log('each')
  counter = new Counter();
});
afterEach((a)= >{
    console.log('after');
});
it("Test counter adds 1 function", () => {
  counter.add(1);
  expect(counter.count).toBe(1);
});
it("Test Counter added 2 functions", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});
Copy the code

Hook functions can be registered multiple times, and typically we scope them by describing

import Counter from "./hook";
let counter = null;
beforeAll((a)= > console.log("before all"));
afterAll((a)= > console.log("after all"));
beforeEach((a)= > {
  counter = new Counter();
});
describe("Partition scope", () => {
  beforeAll((a)= > console.log("inner before")); // The hooks registered here only apply to the test cases currently described
  afterAll((a)= > console.log("inner after"));
  it("Test counter adds 1 function", () => {
    counter.add(1);
    expect(counter.count).toBe(1);
  });
});
it("Test Counter added 2 functions", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});
// before all => inner before=> inner after => after all
// The execution order is much like the Onion model ^-^
Copy the code

4. Configuration files in Jest

We can generate the jest configuration file by using the jest command

npx jest --init
Copy the code

We are prompted to select the configuration item:

➜  unit npx jest --init
The following questions will help Jest to create a suitable configuration for your project
# use jsdom
✔ Choose the test environment that will be used forTesting holds the jsdom (browser - like)# add coverage➤ ➤ Do you want Jest to add coverage reports? ... yesRemove all mocks every time the test is run➤ ➤ Automatically clear mock calls and instances between everytest? ... yesCopy the code

A jest.config.js configuration file is generated in the current directory

5. The Jest coverage

The profile we just generated has been checked to generate coverage reports, so at run time we can simply add the –coverage parameter

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

We can run the NPM run test directly, and the coverage report will be generated under the current project to see the coverage of the current project

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 hook.js  |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.856s, estimated 2s
Copy the code

The command line also has a report prompt, jest increase coverage is very convenient ~

  • Stmts represents statement coverage
  • Branch indicates Branch coverage (if, else)
  • The coverage of Funcs
  • Lines Coverage of Lines of code

At this point, our common use of Jest is almost complete! Stay tuned for the next article to see how to test Vue projects with Jest!