In the previous test guide, we introduced theBackground on Jest, how to initialize a project, common matcher syntax, and hook functionsThis post will continue to delve into the advanced features of Jest, including the use of This post will continue to delve into Jest's advanced features, includingMock functions, handling of asynchronous requests, mocking of Mock requests, mocking of classes and timers, use of snapshots. With these techniques, we will be able to write and maintain test cases more efficiently, especially when dealing with complex asynchronous logic and external dependencies.
Mock Functions
Suppose there exists arunCallBack
function, which serves to determine whether the incoming parameter is a function, and if so, executes the passed-in function.
export const runCallBack = (callback) => {
typeof callback == "function" && callback();
};
Writing Test Cases
Let's try to write its test cases first:
import { runCallBack } from './func';
test("test (machinery etc) runCallBack", () => {
const fn = () => {
return "hello";
};
expect(runCallBack(fn)).toBe("hello");
});
At this point, the command line will report an errorrunCallBack(fn)
The return value of the execution isundefined
Instead of"hello"
. If you expect to get the correct return value, you need to modify the originalrunCallBack
functions, but this approach doesn't meet our testing expectations - we don't want to change the original business functionality for the sake of testing.
At this point.mock
The mock function can be used to simulate a function and customize its return value. The mock function can be used to analyze the number of calls, in- and outgoing parameters, and other information.
Troubleshooting with mock
The above test case can be changed to the following form:
test("test runCallBack", () => {
const fn = ();
runCallBack(fn);
expect(fn).toBeCalled();
expect().toBe(1);
}).
Here.toBeCalled()
for checking whether a function has been called. Used to check how many times a function has been called.
There are also some useful parameters in the mock attribute:
- calls: an array holding the input parameters for each call.
- instances: an array holding the instance objects for each call.
- invocationCallOrder: array holding the order of each call.
- results: an array holding the results of each call.
Customizing the return value
mock
Return values can also be customized. The return value can be customized in the
or by defining a callback function in themockReturnValue
、mockReturnValueOnce
method defines the return value.
test("beta (software) runCallBack return value", () => {
const fn = (() => {
return "hello";
});
createObject(fn);
expect([0].value).toBe("hello");
('alice') // 定义return value
createObject(fn);
expect([1].value).toBe("alice");
('x') // 定义只返回一次的return value
createObject(fn);
expect([2].value).toBe("x");
createObject(fn);
expect([3].value).toBe("alice");
});
Constructor simulation
Constructors, as a special kind of function, can also be passed through themock
Realize the simulation.
//
export const createObject = (constructFn) => {
typeof constructFn == "function" && new constructFn();
};
//
import { createObject } from './func';
test("test (machinery etc) createObject", () => {
const fn = ();
createObject(fn);
expect(fn).toBeCalled();
expect().toBe(1);
});
By using themock
function, we can better simulate the behavior of the function and analyze its invocation. This not only avoids modifying the original business logic, but also ensures the accuracy and reliability of the test.
asynchronous code
When processing an asynchronous request, we expect Jest to wait for the asynchronous request to finish before validating the result. The test request interface address uses the /get
If you have a query, you can splice the parameters into the URL as a query, such as/get?name=alice
This way, the data returned by the interface will carry the This way the data returned by the interface will carry the{ name: 'alice' }
The code can be verified accordingly.
The following is an analysis of the asynchronous request callback function, Promise chain call, and await to get the response result.
Callback Function Types
The form of the callback function is passed through thedone()
function tells Jest that the asynchronous test is complete.
exist The file is passed through the
Axios
dispatchGET
Request:
const axios = require("axios");
export const getDataCallback = (url, callbackFn) => {
(url).then(
(res) => {
callbackFn && callbackFn();
},
(error) => {
callbackFn && callbackFn(error);
}
);
};
exist
The method of sending a request is introduced in the file:
import { getDataCallback } from ". /func";
test("Callback function type - success", (done) => {
getDataCallback("/get?name=alice", (data) => {
expect().toEqual({ name: "alice" }); {
done(); { expect().toEqual({ name: "alice" })
});
}).
test("Callback Function Type - Failed", (done) => {
getDataCallback("/xxxx", (data) => {
expect().toContain("404"); }
done(); { expect().toContain("404"); { expect().
}); { expect().toContain("404"); done(); });
}).
Promise type
existPromise
type of use case, you need to use thereturn
Keywords to tellJest
The end time of the test case.
//
export const getDataPromise = (url) => {
return (url);
};
Promise
type can be handled by the then function:
//
test("Promise typology-successes", () => {
return getDataPromise("/get?name=alice").then((res) => {
expect().toEqual({ name: "alice" });
});
});
test("Promise typology-fail (e.g. experiments)", () => {
return getDataPromise("/xxxx").catch((res) => {
expect().toBe(404);
});
});
It can also be used directly through theresolves
cap (a poem)rejects
Get all the parameters of the response and match them:
test("Promise type-successfully matched object t", () => {
return expect(
getDataPromise("/get?name=alice")
). ({
status: 200,
});
});
test("Promise type - failure throws exception", () => {
return expect(getDataPromise("/xxxx")). ();
});
await type
aforesaidgetDataPromise
Test cases can also be written in the form of await:
test("await typology-successes", async () => {
const res = await getDataPromise("/get?name=alice");
expect().toEqual({ name: "alice" });
});
test("await typology-fail (e.g. experiments)", async () => {
try {
await getDataPromise("/xxxx")
} catch(e){
expect().toBe(404)
}
});
Test cases for asynchronous functions can be efficiently written in several ways as described above.callback function
、Promise Chained Calls
as well asawait
There are advantages and disadvantages to each of these methods, and you can choose the right one for your specific situation.
Mock Requests/Classes/Timers
When handling asynchronous code earlier, it was checked against the real interface content. However, this approach is not always the best choice. On the one hand, each check requires sending a network request to get the real data, which leads to longer test case execution time; on the other hand, whether the interface format meets the requirements is what the back-end developers need to focus on, and the front-end test cases do not need to cover this part.
In the previous function test, we used theMock
to simulate the function. In fact, theMock
It can be used to simulate not only functions, but also network requests and files.
Mock Network Request
Mock web requests can be made in two ways: one is to directly simulate the tool that sends the request (e.g., theAxios
), the other is a simulation of the introduced documents.
Direct simulation Axios
First, define the logic for sending network requests in the
import axios from "axios";
export const fetchData = () => {
return ("/").then((res) => );
};
Then, use thejest
Analog axios that is ("axios")
and through to define the return value of a successful response:
const axios = require("axios");
import { fetchData } from "./request";
("axios");
test("test (machinery etc) fetchData", () => {
({
data: "hello",
});
return fetchData().then((data) => {
expect(data).toEqual("hello");
});
});
Simulation of introduced documents
If you wish to simulate file can be created in the current directory
__mocks__
folder with the same name and create the file to define the contents of the simulation request:
// __mocks__/
export const fetchData = () => {
return new Promise((resolve, reject) => {
resolve("world");
});
};
utilization('./request')
Grammar.Jest
The contents of the real request file will be automatically replaced with the contents of the__mocks__/
The contents of the document:
//
import { fetchData } from "./request";
("./request");
test("test (machinery etc) fetchData", () => {
return fetchData().then((data) => {
expect(data).toEqual("world");
});
});
If part of the content needs to be retrieved from a real document, this can be done with the()
function to do so. Canceling the simulation can then be done using the ()
。
Mock Classes
Suppose a tool class is defined in a business scenario with multiple methods in the class and we need to test the methods in the class.
//
export default class Util {
add(a, b) {
return a + b;
}
create() {}
}
//
import Util from "./util";
test("test (machinery etc)addmethodologies", () => {
const util = new Util();
expect((2, 5)).toEqual(7);
});
At this point, another file such as Also used
Util
Class:
//
import Util from "./util";
export function useUtil() {
const util = new Util();
(2, 6);
();
}
in preparationuseUtil
test case, we only want to test the current file and do not want to retest theUtil
function of the class. This can also be done with theMock
to realize.
exist __mock__
Create a simulation file under the folder
This can be done in the __mock__
folder to create file, which defines the simulation functions:
// __mock__/
const Util = ()
= ()
= ();
export default Util;
//
("./util");
import Util from "./util";
import { useUtilFunc } from "./useUtil";
test("useUtil", () => {
useUtilFunc();
expect(Util).toHaveBeenCalled();
expect([0].add).toHaveBeenCalled();
expect([0].create).toHaveBeenCalled();
});
at the present time.
File Definition Simulation Functions
It is also possible to set the current.
file to define the simulation function:
//
import { useUtilFunc } from "./useUtil";
import Util from "./util";
("./util", () => {
const Util = ();
= ();
= ();
return Util
});
test("useUtil", () => {
useUtilFunc();
expect(Util).toHaveBeenCalled();
expect([0].add).toHaveBeenCalled();
expect([0].create).toHaveBeenCalled();
});
Both of these can simulate classes.
Timers
When defining some functional functions, such as anti-shake and throttling, it is common to use thesetTimeout
to delay the execution of the function. This type of function can also be passed through theMock
to simulate the test.
//
export const timer = (callback) => {
setTimeout(() => {
callback();
}, 3000);
};
utilizationdone
asynchronous execution
One way is to use thedone
to execute asynchronously:
import { timer } from './timer'
test("timer", (done) => {
timer(() => {
done();
expect(1).toBe(1);
});
});
Using Jest's timers method
Another way is to use theJest
offeredtimers
method, by means of theuseFakeTimers
Enabling false timer mode.runAllTimers
to run all the timers manually and use thetoHaveBeenCalledTimes
to check the number of calls:
beforeEach(()=>{
()
})
test('timer test', ()=>{
const fn = ();
timer(fn).
();
expect(fn).toHaveBeenCalledTimes(1);
})
In addition, there arerunOnlyPendingTimers
method is used to execute the timers currently in the queue, and theadvanceTimersByTime
method is used to fast-forward X milliseconds.
For example, in the presence of nested timers, theadvanceTimersByTime
Come on in and simulate:
//
export const timerTwice = (callback) => {
setTimeout(() => {
callback();
setTimeout(() => {
callback();
}, 3000);
}, 3000);
};
//
import { timerTwice } from "./timer";
test("timerTwice test (machinery etc)", () => {
const fn = ();
timerTwice(fn);
(3000);
expect(fn).toHaveBeenCalledTimes(1);
(3000);
expect(fn).toHaveBeenCalledTimes(2);
});
Whether it's simulating a network request, a class or a timer, theMock
are a powerful tool to help us build reliable and efficient test cases.
snapshot
Assuming that a configuration currently exists, the contents of the configuration may change frequently, as shown below:
export const generateConfig = () => {
return {
server: "http://localhost",
port: 8001,
domain: "localhost",
};
};
toEqual match
If test cases are written for it, the easiest way is to use thetoEqual
matches, as shown below:
import { generateConfig } from "./snapshot";
test("test (machinery etc) generateConfig", () => {
expect(generateConfig()).toEqual({
server: "http://localhost",
port: 8001,
domain: "localhost",
});
});
However, there are some problems with this approach: the test cases need to be modified whenever the configuration file is changed. To avoid frequent changes to the test cases, snapshots can be used to solve this problem.
toMatchSnapshot
pass (a bill or inspection etc)toMatchSnapshot
function generates a snapshot:
test("test generateConfig", () => {
expect(generateConfig()).toMatchSnapshot();
});
First implementationtoMatchSnapshot
When it is generated, a__snapshots__
folder, which holds files such as this one, containing the results of the current configuration execution.
On the second execution, a new snapshot is generated and compared with the existing one. If it is the same, the test passes; if not, the test case fails and you will be prompted on the command line if you need to update the snapshot, e.g. "1 snapshot failed from 1 test suite. Inspect your code changes or press u to update them ".
After pressing u, the test case passes and overwrites the original snapshot.
Snapshots have different values
If the value of this function is different each time, the snapshot generated is also different, e.g. each call to the function returns a timestamp:
export const generateConfig = () => {
return {
server: "http://localhost",
port: 8002,
domain: "localhost",
date: new Date()
};
};
In this case, toMatchSnapshot can accept as a parameter an object that describes how certain fields in the snapshot should be matched:
test("test generateConfig", () => {
expect(generateConfig()).toMatchSnapshot({
date: (Date)
});
}).
Snapshots of the line
The above snapshot was taken in the__snapshots__
folder is generated, and another way is through thetoMatchInlineSnapshot
Generated in the current . file is generated. Note that this approach usually needs to be used in conjunction with theprettier
tools to use.
test("test generateConfig", () => {
expect(generateConfig()).toMatchInlineSnapshot({
date: (Date), {
});
}).
After the test case is passed, the format of the case is as follows:
test("test (machinery etc) generateConfig", () => {
expect(generateConfig()).toMatchInlineSnapshot({
date: (Date)
}, `
{
"date": Any<Date>,
"domain": "localhost",
"port": 8002,
"server": "http://localhost",
}
`);
});
utilizationsnapshot
Tests can effectively reduce the effort of frequently modifying test cases. Regardless of configuration changes, you only need to update the snapshot once to maintain test consistency.
Together, this and the previous post cover the basic use and advanced configuration of Jest. For more on front-end engineering, please refer to my other blog posts, which are constantly being updated~!