Jest
测试示例
// 测试流程
import fn from 'XX'; // 1. 导入要测试的函数
test('测试 fn 函数', () => {
expect( fn('参数1') ).toBe(100); // 2. 给函数传入对应的参数, 检测对应的结果
expect( fn('参数1', '参数2') ).not.toEqual(100); // 2. 给函数传入对应的参数, 检测对应的结果
});
test('2 + 2 等于 4', () => {
expect(2 + 2).toBe(4);
});
test('对象赋值', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2}); // toEqual 是 递归的检查对象或数组的每个字段
});
toBe VS toEqual
toEqual 是递归的判断对象的每个值是否相等。 toBe 只判断对象的地址是否相同。
const can1 = {
flavor: 'grapefruit',
ounces: 12,
};
const can2 = {
flavor: 'grapefruit',
ounces: 12,
};
const can3 = can1
describe('toBe VS toEqual', () => {
test('相等', () => {
expect(can1).toEqual(can2);
});
test('不相等', () => {
expect(can1).not.toBe(can2);
});
test('相等', () => {
expect(can1).toBe(can3);
});
});
判断真值
有时候我们想要区分 undefined
null
false
, 但是有时候我们不希望区分
toBeNull
仅匹配null
toBeUndefined
仅匹配undefined
toBeDefined
和toBeUndefined
结果相反toBeTruthy
匹配if
语句中视为真值的任何内容 (不区分)toBeFalsy
匹配if
语句中视为假值的任何内容 (不区分)
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
Number
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3); // 是否大于3
expect(value).toBeGreaterThanOrEqual(3.5); // 是否大于等于3.5
expect(value).toBeLessThan(5); // 是否小于5
expect(value).toBeLessThanOrEqual(4.5); // 是否小于等于4.5
// toBe | toEqual 用来判断相等
expect(value).toBe(4);
expect(value).toEqual(4);
});
// 浮点数判断 用 toBeCloseTo 代替 toEqual
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); // 错误, 因为精度丢失的原因
expect(value).toBeCloseTo(0.3); // 正确
});
String
test('使用 toMatch 来进行正则校验', () => {
expect('team').not.toMatch(/I/);
});
test('使用 toMatch 来进行正则校验', () => {
expect('Christoph').toMatch(/stop/);
});
Array 和 iterable
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('toContain 判断 arr 中是否存在 milk', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
异常
如果您想测试特定函数在调用时是否抛出错误,使用 toThrow
。
// 抛出异常的函数需要在包装函数中调用,否则toThrow断言将失败
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
});
异步
Promise
从测试中返回一个Promise,Jest将等待该Promise解决。例如,我们假设fetchData返回一个promise,它被认为解析为字符串'peanut butter'。我们可以用
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Async / Await
另外,您也可以在测试中使用async和await。要编写异步测试,在传递给test的函数前使用async关键字。例如,可以使用相同的fetchData场景进行测试
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1); // 验证在测试期间是否调用了一定数量的断言。在测试异步代码时,这通常很有用,以确保回调中的断言确实被调用了。
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
Callbacks
如果你不使用Promise,你可以使用回调。例如,fetchData不是返回一个promise,而是期望一个回调,即获取一些数据并在完成时调用callback(null, data)。您希望测试返回的数据是否是字符串'peanut butter'。默认情况下,Jest测试在执行结束时完成。这意味着这个测试无法按预期进行
// 不能这样使用。 问题是,在调用回调之前,测试将在fetchData完成后立即完成。
test('the data is peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
// 有另一种测试形式可以解决这个问题。与其将测试放在带有空参数的函数中,不如使用一个名为done的参数。Jest将在完成测试之前等待,直到done回调被调用。
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
如果从未调用 done()
,则测试将失败(带有超时错误),这正是您希望发生的情况。 如果expect语句失败,它会抛出一个错误,并且不会调用done()。如果我们想在测试日志中看到它失败的原因,我们必须将expect 放在 try ... cache
块中,并将catch块中的错误传递给done
Setup & Teardown
通常在编写测试时,在测试运行之前需要进行一些设置工作,在测试运行之后需要进行一些完成工作。Jest提供了帮助函数来处理这个问题。
重复设置
如果您需要对许多测试重复执行一些工作,您可以使用 beforeEach 和 afterEach 钩子。例如,假设多个测试与数据库交互。在每个测试之前都必须调用一个方法initializeCityDatabase(),在每个测试之后都必须调用一个方法clearCityDatabase()。你可以用
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
一次性设置
beforeAll(() => {
return initializeCityDatabase();
});
afterAll(() => {
return clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
作用域
beforeEach(() => {
return initializeCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
describe('matching cities to foods', () => {
// 仅应用于此描述块中的测试
beforeEach(() => {
return initializeFoodDatabase();
});
test('Vienna <3 veal', () => {
expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
});
test('San Juan <3 plantains', () => {
expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
});
});
模拟函数
模拟函数允许通过以下方式测试代码之间的联系 : 删除函数的实际实现、捕获对函数的调用(以及在这些调用中传递的参数)、在使用new实例化时捕获构造函数的实例,以及允许对返回值进行测试时配置。
有两种方法来模拟函数:创建一个在测试代码中使用的模拟函数,或者编写一个手动模拟来覆盖模块依赖项。
// 假设我们正在测试函数 forEach 的实现,该函数为所提供的数组中的每个项调用回调。
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
// 为了测试这个函数,我们可以使用一个模拟函数,并检查模拟的状态,以确保按预期调用回调函数。
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// 模拟函数调用了2次
expect(mockCallback.mock.calls.length).toBe(2);
// 函数第一次调用的第一个参数是0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 函数第二次调用的第一个参数是1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 函数的第一次调用的返回值是42
expect(mockCallback.mock.results[0].value).toBe(42);
.mock 属性
所有模拟函数都有这个特殊的 .mock 属性,在该属性中保存有关函数如何调用以及函数返回内容的数据。mock属性还会对每次调用跟踪this的值,因此也可以检查它
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
这些模拟成员在测试中很有用,可以断言这些函数如何被调用、实例化或返回什么
// fn 只调用了一次
expect(someMockFunction.mock.calls.length).toBe(1);
// 函数第一次调用的第一个参数是'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// 函数第一次调用的第二个参数是'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// 函数第一次调用的返回值是“return value”
expect(someMockFunction.mock.results[0].value).toBe('return value');
// 函数被调用时带有特定的this上下文: element 对象。
expect(someMockFunction.mock.contexts[0]).toBe(element);
// 这个函数实例化了两次
expect(someMockFunction.mock.instances.length).toBe(2);
// 函数第一次实例化返回的对象拥有一个名为name的属性,其值被设置为test
expect(someMockFunction.mock.instances[0].name).toEqual('test');
// 函数最后一次调用的第一个参数是'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
模拟返回值
模拟函数还可以用于在测试期间向代码中注入测试值
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
// ================================================
const filterTestFn = jest.fn();
// 第一次返回 true, 第二次返回 false
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
模拟包
// users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
// user.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// 或者您可以根据您的用例使用以下内容, axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
模拟部分函数
// foo-bar-baz.js
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';
jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});
test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();
expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});
Mock Implementations
在某些情况下,超出指定返回值的能力并完全替换模拟函数的实现是有用的。这可以用 jest.fn
来完成 或 mockImplementationOnce
方法。
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val)); // > true
当您需要定义从另一个模块创建的模拟函数的默认实现时,mockImplementation方法非常有用
// foo.js
module.exports = function () {
// 执行一些代码;
};
// test.js
jest.mock('../foo');
const foo = require('../foo');
// foo 是模拟的fn
foo.mockImplementation(() => 42);
foo();// > 42
当您需要重新创建模拟函数的复杂行为,以便多个函数调用产生不同的结果时,请使用mockImplementationOnce方法
const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
myMockFn((err, val) => console.log(val)); // > true
myMockFn((err, val) => console.log(val)); // > false
///////////////
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'
对于通常是链式方法(因此总是需要返回this)的情况,我们有一个语法糖来简化它,以 .mockReturnThis()
函数的形式,该函数也位于所有mock上
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// 和下面一样
const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};