测试的思路
- 引入要测试的函数
- 给函数一个输入
- 定义预期输出
- 检查函数是否返回了预期的输出结果
// demo.js
function sum (a, b) {
return a + b;
}
function substract (x, y) {
return x - y;
}
module.exports = {
sum,
substract
}
- 我们常见Jest的断言语法大概是这样子
expect(sum(1, 2)).toBe(3)
- 大概实现思路
// demo.test.js
function expect(result) {
return {
toBe(expected) {
if (result !== expected) {
throw new Error(`期望结果是${expected},但是收到了${result}`);
} else {
console.log('Pass');
}
}
}
}
expect(sum(1, 2)).toBe(3)
- 输出结果
- 为了更好的捕获报错信息,我们需要输出日志,一般会这么写 测试用例
test('sum(1, 2)的结果应该是3', () => {
expect(sum(1, 2)).toBe(3)
})
// demo.test.js
test('sum(1, 2)的结果应该是3', () => {
// 一个测试用例中可以有多个断言函数
expect(sum(1, 2)).toBe(3)
expect(sum(2, 2)).toBe(4)
})
test('substract(1, 2)的结果应该是1', () => {
expect(substract(3, 2)).toBe(1)
})
function test (message, callback) {
try {
callback()
} catch (err) {
console.error(`${message}: ${err.message}`);
}
}
- 输出结果
体验Jest
- 安装
npm install --save-dev jest @types/jest
// package.json
"scripts": {
"test": "jest"
}
// 在项目内自定义jest配置
npx jest --init
Jest CLI选项-
--watchAll监视文件的更改并在任何更改时重新运行所有测试--watch想要重新运行仅基于已更改文件的测试, 依赖git- 单独运行某个文件
npm run test -- xxx.test.js
在jest中使用es6模块
npm install --save-dev babel-jest @babel/core @babel/preset-env
// babel.config.js
module.exports = {
presets: [['@babel/preset-env', {
targets: {node: 'current'}
}]]
}
- 此时可以使用
import,export语法
// const { sum, substract } = require('./demo');
import { sum, substract } from './demo';
Jest全局APT
- 在测试文件中,Jest将所有的这些方法和对象放在全局环境中,你可以无需导入即可使用,当然你也可以显示导入
import { describe, expect, test } from '@jest/global'
Test函数
test函数别名: it(name, fn, timeout)
test.only(name, fn, timeout) 当前测试文件下只执行当前test用例
Expect匹配器
- 在编写测试时,通常需要检查是否满足某些条件。
Expect使你可以访问许多‘匹配器’,以使你可以验证不同的内容
test.only('测试expect', () => {
expect(2 + 2).toBe(4)
expect({name: 'zhangwh'}).toEqual({name: 'zhangwh'})
expect('Christoph').toMatch(/stop/) // 匹配正则
expect(4).toBeGreaterThan(3) // 期望4>3
expect(4).toBeLessThan(5) // 期望4<5
})
describe函数
describe创建一个将几个相关测试组合在一起的块。
describe('a 组件', () => {
test('a 功能', () => {
console.log('a组件 xx 功能')
})
test('a 功能', () => {
console.log('a组件 oo 功能')
})
})
assertions函数
expect.assertions(1); // 至少得有一次断言
生命周期钩子
afterAll(fn, timeout)afterEach(fn, timeout)beforerAll(fn, timeout)beforeEach(fn, timeout)每个测试用例执行之前执行一次
Jest对象
jest对象会自动注入到每个测试文件中,jest对象上的方法帮助你创建模拟并让你控制 Jest 的整体行为。 也可以通过import { jest } from '@jest/globals'手动进行引入
Jest测试异步代码
setTimeout
function getData(callback) {
setTimeout(() => {
callback({foo: 'bar'});
}, 2000);
}
test('async getData', (done) => {
getData((data) => {
done(); // 执行异步
expect(data).toEqual({foo: 'bar'});
})
})
Promises
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({foo: 'bar'});
}, 2000);
})
}
// 写法1
test('async getData', (done) => {
getData().then((data) => {
done(); // 执行异步
expect(data).toEqual({foo: 'bar'});
})
})
// 写法2
test('async getData', () => {
return getData().then((data) => {
expect(data).toEqual({foo: 'bar'});
})
})
.resloves/.rejects
// 写法3
test('async getData', () => {
return expect(getData()).resolves.toEqual({foo: 'bar'});
})
Async/Await
// 写法4
test('async getData', async () => {
const data = await getData();
expect(data).toEqual({foo: 'bar'});
})
Mock定时器
- 可以快速所有定时器结束
jest.runAllTimers();jest.useFakeTimers();
// mock 定时器取代setTimeout
jest.useFakeTimers();
test('async getData', () => {
expect.assertions(1); // 至少得有一次断言
// 异步任务 下面这种写法不写return 正常来说expect不会执行
// 这里使用了useFakeTimers, runAllTimers
getData().then((data) => {
expect(data).toEqual({foo: 'bar'});
})
// 快速所有定时器结束
jest.runAllTimers();
})
- 计时器可以恢复他们默认的行为通过
jest.useRealTimers() - 涉及到递归调用,运行等待计时器,会计当前进行的定时器结束,不等待其他的
jest.runOnlyPendingTimers()
// 如果涉及到递归调用
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({foo: 'bar'});
// 递归
getData();
}, 2000);
})
}
jest.useFakeTimers();
test('async getData', () => {
expect.assertions(1); // 至少得有一次断言
getData().then((data) => {
expect(data).toEqual({foo: 'bar'});
})
// 运行等待计时器
jest.runOnlyPendingTimers();
})
jest.advancertimersbytime ()快进多少ms
Mock函数
- 假设我们要测试函数
forEach的内部实现,这个函数为传入的数组中的每个元素调用一次回调函数
// forEach.js
function forEach(arr, callback) {
for (let index = 0; index < arr.length; index++) {
callback(arr[index], index);
}
}
- 为了测试此函数,我们可以使用一个 mock 函数,然后检查 mock 函数的状态来确保回调函数如期调用。
test('mock fn', () => {
const arr = [1, 2, 3];
const mockFn = jest.fn();
forEach(arr, mockFn);
expect(mockFn.mock.calls.length).toBe(3); // passed
// calls: [ [1, 0], [2, 1], [3, 2] ]
expect(mockFn.mock.calls[0][1]).toBe(0); // passed
})
test('mock fn', () => {
const arr = [1, 2, 3];
const mockFn = jest.fn((val, index) => {
return val + 1;
});
forEach(arr, mockFn);
console.log(mockFn.mock);
expect(mockFn.mock.calls.length).toBe(3);
// calls: [ [1, 0], [2, 1], [3, 2] ]
expect(mockFn.mock.calls[0][1]).toBe(0);
expect(mockFn.mock.results[0].value).toBe(2);
})
Mock 的返回值
- Mock 函数也可以用于在测试期间将测试值注入代码
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
Mock 的模拟模块
- 模拟
axios响应 - 为测试该方法而不实际调用 API (使测试缓慢与脆弱),我们可以用
jest.mock(...)函数自动模拟 axios 模块 - 一旦模拟模块,我们可为
.get提供一个mockResolvedValue,它会返回假数据用于测试。 实际上,我们想说的是我们想让axios.get('/users.json')有个伪造的响应结果
// user.js
import axios from 'axios';
export const getAllusers = () => {
return axios.get('/users.json').then((response) => response.data)
}
// user.test.js
import axios from 'axios';
import { getAllusers } from './user';
// 模拟axios 实际调用的是axios.get.mockResolvedValue(response);
jest.mock('axios');
test('should fetch users', async () => {
const users = [{ name: 'Agony' }];
const response = { data: users };
axios.get.mockResolvedValue(response);
const data = await getAllusers();
expect(data).toEqual(users);
})
Mock模拟函数实现(Mock Implementations)
// foo.js
// 正常返回111
export default function() {
return 111
}
// foo.test.js
jest.mock('./foo')
import foo from './foo';
foo.mockImplementation(() => 123); // 模拟函数内部逻辑
test('mock implementation', () => {
expect(foo()).toBe(123); // Passed
})
Jest中的钩子函数
- 如果你有一些要为多次测试重复设置的工作,你可以使用
beforeEach和afterEach beforeEach运行每个测试用例之前执行afterEach每个测试用例执行结束之后执行beforeAll在所有测试用例之前仅执行 1 次aftereAll在所有测试用例执行结束之后仅执行 1 次
钩子函数的作用域
- 顶层的
before*和after*hook 函数会应用于文件中的每一条测试 - 在
describe块中声明的 hook 函数,只会作用于describe中的测试 - 注意顶级的
beforeEach会比describe中的beforeEach执行的更早。 - 注意顶级的
afterEach会比describe中的afterEach执行的更晚。