Front-end automated testing

Last article We've talked about the basic usage of Jest, and in this article we'll talk about how to use Jest in depth.

In testing, we will encounter many problems, such as how to test asynchronous logic, how to mock interface data and so on.

Through this article, you will be able to use Jest in the development of more than one edge, let's break through the various doubts one by one!

1.Jest Advanced Use

Testing of 1.1 Asynchronous Function

Asynchronism can only be mentioned in two cases, one is the way of callback function, the other is the popular promise way.

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

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

Let's write the async.test.js method

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

// The default test case won't wait for the test to complete, so add the do parameter and call the do function when it's done
it('Test the incoming callback function for asynchronous return results',(done)=>{ // Asynchronous testing methods can be done by
    getDataThroughCallback((data)=>{
        expect(data).toEqual({
          name:'webyouxuan'
        });
        done();
    })
})
// Returning a promise waits for the promise to complete
it('test promise Return result 1',()=>{
    return getDataThroughPromise().then(data=>{
        expect(data).toEqual({
          name:'webyouxuan'
        });
    })
})
// Direct use of async + await grammar
it('test promise Return result 2',async ()=>{
    let data = await getDataThroughPromise();
    expect(data).toEqual({
      name:'webyouxuan'
    });
})
// Using a self-contained matcher
it('test promise Return result 3',async ()=>{
    expect(getDataThroughPromise()).resolves.toMatchObject({
      name:'webyouxuan'
    })
})

2. mock in Jest

2.1 Simulating function jest.fn()

Why simulate functions? Let's look at the following scenario, how do you test it

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

At first glance, it's very simple. We just need to judge the return result of the function, 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]);
});

But we want to be more detailed, such as whether each call function passes in every item of the array, whether the function has been called three times, more specifically, to trace the specific execution process of the function!

import { myMap } from "./map";
it("test map Method", () => {
  // Functions declared through jest.fn can be traced back
  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 function returns a value of 2,4,6.
  expect(fn.mock.results.map(item=>item.value)).toEqual([2,4,6])
});

Look at what's in this mock in detail.

2.2 Simulated file jest.mock()

We want to mock the interface. We can create a file with the same name directly in the _mocks_ directory and mock the whole file. For example, the current file is called api.js.

import axios from "axios";

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

Create _mocks_/api.js

export const fetchUser = ()=>{
    return new Promise((resolve,reject)=> resolve({user:'webyouxuan'}))
}
export const fetchList = ()=>{
    return new Promise((resolve,reject)=>resolve(['Banana','Apple']))
}

Start testing

jest.mock('./api.js'); // Using api.js under _mocks__
import {fetchList,fetchUser} from './api'; // Method of introducing mock
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'])
})

It is important to note that if mock's api.js method is incomplete and the original file method may need to be introduced in the test, then the real file needs to be introduced using jest.requireActual('./api.js').

Here we need to consider whether it is troublesome to do so, in fact, just want to drop the real request mock, so can we directly mock axios method?

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']);
            }
        })
    }
}

By default, _mocks_/axios.js is found 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'])
})

2.3 Simulated Timer

Next, let's look at this case. We want to pass in a callback and see if it can be called.

export const timer = callback=>{
    setTimeout(()=>{
        callback();
    },2000)
}

So it's easy to write such test cases.

import {timer} from './timer';
it('callback Will it be executed?',(done)=>{
    let fn = jest.fn();
    timer(fn);
    setTimeout(()=>{
        expect(fn).toHaveBeenCalled();
        done();
    },2500)
});

Do you think it's silly, if it takes a long time? How many timers are there? Then we think of mock Timer.

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

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

3. Hook function in Jest

For testing convenience, a hook function similar to Vue is also provided in Jest, which can be executed before or after execution of test cases.

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

We're going to test whether the add method in Counter class meets expectations and write test cases.

import Counter from './hook'
it('test  counter Increase 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 Increase 2 functions',()=>{
    let counter = new Counter;
    counter.add(2);
    expect(counter.count).toBe(2)
})

We find that each test case needs to be tested based on a new counter instance to prevent the interaction between test cases. At this time, we can put repetitive logic into the hook!

hook

  • beforeAll is executed before all test cases are executed
  • After all test cases are executed, afteraAll
  • Before Each Use Case Executes
  • After Each Use Case Executes
import Counter from "./hook";
let counter = null;
beforeAll(()=>{
    console.log('before all')
});
afterAll(()=>{
    console.log('after all')
});
beforeEach(() => {
  console.log('each')
  counter = new Counter();
});
afterEach(()=>{
    console.log('after');
});
it("test  counter Increase 1 function", () => {
  counter.add(1);
  expect(counter.count).toBe(1);
});
it("test  counter Increase 2 functions", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});

Hook functions can be registered many times. Generally, we use describe to divide scope.

import Counter from "./hook";
let counter = null;
beforeAll(() => console.log("before all"));
afterAll(() => console.log("after all"));
beforeEach(() => {
  counter = new Counter();
});
describe("Scope delimitation", () => {
  beforeAll(() => console.log("inner before")); // The hook registered here is only valid for test cases under the current description
  afterAll(() => console.log("inner after"));
  it("test  counter Increase 1 function", () => {
    counter.add(1);
    expect(counter.count).toBe(1);
  });
});
it("test  counter Increase 2 functions", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});
// before all => inner before=> inner after => after all
// The order of execution is very similar to the onion model^-^

4. Configuration files in Jest

We can generate jest configuration files through the jest command

npx jest --init

We will be prompted to select configuration items:

➜  unit npx jest --init
The following questions will help Jest to create a suitable configuration for your project
# Using jsdom
✔ Choose the test environment that will be used for testing › jsdom (browser-like)
# Adding coverage
✔ Do you want Jest to add coverage reports? ... yes
# Clear all mock s every time you run the test
✔ Automatically clear mock calls and instances between every test? ... yes

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

5.Jest coverage

The configuration file we just generated has been checked to generate coverage reports, so we can add the -- coverage parameter directly at run time.

"scripts": {
    "test": "jest --coverage"
}

The npm run test can be executed directly, at which point coverage reports will be generated under our 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

There will also be reports on the command line, and it's very convenient for jest to increase coverage~

  • Stmts Representation Statement Coverage
  • Branch represents branch coverage (if, else)
  • Coverage of Funcs Functions
  • Lines code line coverage

So far, our common use of Jest is almost the same! In the next article, we'll see how to use Jest to test the Vue project. Please look forward to it!

Tags: Javascript axios Vue npm

Posted on Tue, 10 Sep 2019 02:15:05 -0700 by aashcool198