官网 https://jestjs.io/docs/getting-started
安装
cnpm i --save-dev jest
使用
- 在项目中的任意位置(通常单独建个名为 test 的文件夹)新建以
.test.js
为后缀的测试文件,如expect.test.js
(若项目中有 test.js 存在,请改为其他名称,JEST 会将 test.js 也视为测试文件)
- 在 package.json 中添加测试脚本
"scripts": { "test": "jest",
则执行 npm run test 时,便会使用 jest 执行所有的 .test.js
为后缀的测试文件
expect.test.js
中添加内容
test("断言2+2=4", () => { expect(2 + 2).toBe(4); });
以上即一个测试案例,用于断言 2+2=4
此处的 test 也可以用 it
- 执行 npm run test
vscode中可以直接点击脚本执行按钮
- 效果如下:
全部绿色,即测试通过。
若想测试一直执行,可以使用
npx jest 测试文件名称 --watch
断言
相等 toBe
expect(2 + 2).toBe(4);
执行的是 ES6 的Object.is,与严格相等运算符(===)基本一致,不会进行强制类型转换,不同之处为 +0不等于-0,NaN等于自身,对引用类型的数据(如对象、数组等),比较其地址是否相同。
相等 toEqual
会比较数组/对象的每一项,但会忽略 undefined
// 测试通过 test("toEqual断言对象相等", () => { expect({ a: 1 }).toEqual({ a: 1, b: undefined }); });
严格相等 toStrictEqual
与 toEqual 类似,但不会忽略 undefined
// 测试报错 test("toStrictEqual断言对象相等", () => { expect({ a: 1 }).toStrictEqual({ a: 1, b: undefined }); });
不相等 not
在判断相等前添加 not 即可
test("断言2+3不等于4", () => { expect(2 + 3).not.toBe(4); });
特殊值的断言
- toBeNull() 断言为 null
- toBeUndefined() 断言为 undefined
- toBeTruthy() 断言为 true
- toBeFalsy() 断言为 false
比较
- toBeGreaterThan(3) 大于3
- toBeGreaterThanOrEqual(3.5) 大于等于3.5
- toBeLessThan(5) 小于5
- toBeLessThanOrEqual(4.5) 小于等于4.5
浮点数的计算结果比较 toBeCloseTo
因 js 无法精确计算浮点数,不能用 toBe ,而要用 toBeCloseTo
test('断言 0.1+0.2=0.3', () => { const value = 0.1 + 0.2; //expect(value).toBe(0.3); 此方式会断言失败 expect(value).toBeCloseTo(0.3); });
字符串包含 toMatch
// Christoph 中包含 stop expect('Christoph').toMatch(/stop/);
数组中查找指定项 toContain
expect(shoppingList).toContain('milk');
更多断言用法见 https://jestjs.io/docs/expect#expectvalue
异步测试
回调函数
const callbackFunc = (cb) => { setTimeout(() => { cb("hello"); }, 100); }; test("callback", (done) => { callbackFunc((data) => { //等待异步执行 done(); expect(data).toBe("hello"); }); });
Promise
const promiseFuc = () => Promise.resolve("hello"); test("promise", () => { return promiseFuc().then((data) => { expect(data).toBe("hello"); }); });
await 写法
const promiseFuc = () => Promise.resolve("hello"); test("await async", async () => { const data = await promiseFuc(); expect(data).toBe("hello"); });
属性 resolves 写法
const promiseFuc = () => Promise.resolve("hello"); test("resolves", () => { return expect(promiseFuc()).resolves.toBe("hello"); });
属性 rejects 写法
const promiseFuc = () => Promise.reject("error"); test("rejects", () => { return expect(promiseFuc()).rejects.toBe("error"); });
模拟 mock
模拟数据
function mockTestFunc(cb) { return cb(3); } test("mock", () => { // jest.fn() 是对 mock 过程的监听 const mockCB = jest.fn(); // 调用mock函数 mockTestFunc(mockCB); // 测试mock函数是否执行 expect(mockCB).toHaveBeenCalled(); // 测试mock函数回调的参数是否为3 expect(mockCB).toHaveBeenCalledWith(3); // 测试mock函数回调的次数是否为1 expect(mockCB).toHaveBeenCalledTimes(1); // 打印mock对象,可查看相关属性 console.log(mockCB.mock); });
打印内容为:
{ calls: [ [ 3 ] ], contexts: [ undefined ], instances: [ undefined ], invocationCallOrder: [ 1 ], results: [ { type: 'return', value: undefined } ], lastCall: [ 3 ] }
上例中没有对回调的参数进行二次处理,所以 value 为 undefined
jest.fn() 中可以对回调的参数进行二次处理,得到对应的值 value
function mockTestFunc(cb) { return cb(3); } test("mock二次处理", () => { // jest.fn() 是对 mock 过程的监听 const mockCB = jest.fn((x) => x * 2); // 调用mock函数 mockTestFunc(mockCB); // 打印mock对象,可查看相关属性 console.log(mockCB.mock); });
打印结果为:
{ calls: [ [ 3 ] ], contexts: [ undefined ], instances: [ undefined ], invocationCallOrder: [ 1 ], results: [ { type: 'return', value: 6 } ], lastCall: [ 3 ] }
得到值 value 为 6
模拟第三方库
真实请求如下:
user.js
const axios = require("axios"); module.exports = function getUserName(id) { return axios .get(`https://jsonplaceholder.typicode.com/users/${id}`) .then((res) => { return res.data.username; }); };
mock.test.js
const getUserName = require("./user.js"); test("真实请求第三方库-axios", () => { return getUserName(1).then((name) => { console.log(name); }); });
mock 写法1
const getUserName = require("./user.js"); const axios = require("axios"); jest.mock("axios"); axios.get.mockImplementation(() => { return Promise.resolve({ data: { username: "朝阳", }, }); }); test("mock 第三方库-axios", () => { return getUserName(1).then((name) => { console.log(name); }); });
此时的 mockCB 即 axios.get ,可以进行监听
test("mock 第三方库-axios", () => { return getUserName(1).then((name) => { // 监听 axios.get 是否被调用 expect(axios.get).toHaveBeenCalled(); }); });
mock 写法2
使用 mockResolvedValue 直接模拟 Promise 的返回值。
axios.get.mockResolvedValue({ data: { username: "朝阳", }, });
mock 写法3 【推荐】
项目目录下新建文件夹 __mocks__
,在__mocks__
文件夹中按模拟的第三方库名称新建 js 文件,如 axios.js
,内容为
const axios = { get: jest.fn(() => Promise.resolve({ data: { username: "朝阳" } })), }; module.exports = axios;
如此,则测试文件中的 axios 请求,都会按 axios.js 中逻辑执行。
const getUserName = require("./user.js"); test("mock 第三方库-axios", () => { return getUserName(1).then((name) => { console.log(name); }); });
会打印 朝阳
时间控制
执行所有定时器
const callbackFunc = (cb) => { setTimeout(() => { cb("hello"); }, 1000); }; test("回调是否执行", () => { const callback = jest.fn(); callbackFunc(callback); // 测试 callback 是否执行 expect(callback).toHaveBeenCalled(); });
此处因 setTimeout 导航回调函数 1秒后 执行,所以测试会报错
解决方案是通过 jest 接管时间控制,修正后代码如下(详见备注):
// jest 接管时间控制 jest.useFakeTimers(); test("回调是否执行", () => { const callback = jest.fn(); callbackFunc(callback); // 执行所有定时器 jest.runAllTimers(); expect(callback).toHaveBeenCalled(); });
分步执行定时器
// setTimeout的嵌套:1秒后调用 one ,再过2秒后调用 two const callbackFunc = (cb) => { setTimeout(() => { cb("one"); setTimeout(() => { cb("two"); }, 2000); }, 1000); }; // jest 接管时间控制 jest.useFakeTimers(); test("回调是否执行", () => { const callback = jest.fn(); callbackFunc(callback); // 执行一个定时器 jest.runOnlyPendingTimers(); expect(callback).toHaveBeenLastCalledWith("one"); // 又执行一个定时器 jest.runOnlyPendingTimers(); expect(callback).toHaveBeenLastCalledWith("two"); });
指定代码运行的时间
jest.advanceTimersByTime(代码执行时间),单位为毫秒
const callbackFunc = (cb) => { setTimeout(() => { cb("one"); }, 1000); }; // jest 接管时间控制 jest.useFakeTimers(); test("回调是否执行", () => { const callback = jest.fn(); callbackFunc(callback); // 500毫秒后 jest.advanceTimersByTime(500); // 1秒后才会执行,测试报错! expect(callback).toHaveBeenLastCalledWith("one"); });
test("回调是否执行", () => { const callback = jest.fn(); callbackFunc(callback); // 500毫秒后 jest.advanceTimersByTime(500); // 再次500毫秒后 jest.advanceTimersByTime(500); // 测试通过! expect(callback).toHaveBeenLastCalledWith("one"); });