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!