期望
当你编写测试时,你经常需要检查值是否满足某些条件。 expect
使你可以访问许多 "matchers",让你验证不同的事物。
有关 Jest 社区维护的其他 Jest 匹配器,请查看 jest-extended
。
仅当你显式导入 Jest API 时,此页面中的 TypeScript 示例才会按照文档说明的方式工作:
import {expect, jest, test} from '@jest/globals';
有关如何使用 TypeScript 设置 Jest 的详细信息,请参阅 入门 指南。
参考
- 期望
- 修饰符
- 匹配器
.toBe(value)
.toHaveBeenCalled()
.toHaveBeenCalledTimes(number)
.toHaveBeenCalledWith(arg1, arg2, ...)
.toHaveBeenLastCalledWith(arg1, arg2, ...)
.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)
.toHaveReturned()
.toHaveReturnedTimes(number)
.toHaveReturnedWith(value)
.toHaveLastReturnedWith(value)
.toHaveNthReturnedWith(nthCall, value)
.toHaveLength(number)
.toHaveProperty(keyPath, value?)
.toBeCloseTo(number, numDigits?)
.toBeDefined()
.toBeFalsy()
.toBeGreaterThan(number | bigint)
.toBeGreaterThanOrEqual(number | bigint)
.toBeLessThan(number | bigint)
.toBeLessThanOrEqual(number | bigint)
.toBeInstanceOf(Class)
.toBeNull()
.toBeTruthy()
.toBeUndefined()
.toBeNaN()
.toContain(item)
.toContainEqual(item)
.toEqual(value)
.toMatch(regexp | string)
.toMatchObject(object)
.toMatchSnapshot(propertyMatchers?, hint?)
.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)
.toStrictEqual(value)
.toThrow(error?)
.toThrowErrorMatchingSnapshot(hint?)
.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)
- 不对称匹配器
expect.anything()
expect.any(constructor)
expect.arrayContaining(array)
expect.not.arrayContaining(array)
expect.closeTo(number, numDigits?)
expect.objectContaining(object)
expect.not.objectContaining(object)
expect.stringContaining(string)
expect.not.stringContaining(string)
expect.stringMatching(string | regexp)
expect.not.stringMatching(string | regexp)
- 断言计数
- 扩展实用程序
期望
expect(value)
每次要测试值时都会使用 expect
函数。 你很少会单独调用 expect
。 相反,你将使用 expect
和 "matcher" 函数来断言某个值的某些内容。
通过一个例子更容易理解这一点。 假设你有一个方法 bestLaCroixFlavor()
,它应该返回字符串 'grapefruit'
。 你可以通过以下方式进行测试:
test('the best flavor is grapefruit', () => {
expect(bestLaCroixFlavor()).toBe('grapefruit');
});
在本例中,toBe
是匹配器函数。 下面记录了许多不同的匹配器函数,可以帮助你测试不同的东西。
expect
的参数应该是代码生成的值,并且匹配器的任何参数都应该是正确的值。 如果你把它们混合起来,你的测试仍然可以工作,但是失败的测试的错误消息看起来会很奇怪。
修饰符
.not
如果你知道如何测试某些东西,.not
可以让你测试它的反面。 例如,此代码测试最好的 La Croix 风味不是椰子:
test('the best flavor is not coconut', () => {
expect(bestLaCroixFlavor()).not.toBe('coconut');
});
.resolves
使用 resolves
来解开已履行 promise 的值,以便可以链接任何其他匹配器。 如果 promise 被拒绝,则断言失败。
例如,此代码测试 Promise 是否解析以及结果值是否为 'lemon'
:
test('resolves to lemon', () => {
// make sure to add a return statement
return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});
由于你仍在测试 Promise,因此测试仍然是异步的。 因此,你需要通过返回未封装的断言来 让杰斯特稍等一下。
或者,你可以将 async/await
与 .resolves
结合使用:
test('resolves to lemon', async () => {
await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus');
});
.rejects
使用 .rejects
来解开被拒绝的 Promise 的原因,以便可以链接任何其他匹配器。 如果 promise 得到履行,则断言失败。
例如,此代码测试 Promise 是否因原因 'octopus'
而拒绝:
test('rejects to octopus', () => {
// make sure to add a return statement
return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
'octopus',
);
});
由于你仍在测试 Promise,因此测试仍然是异步的。 因此,你需要通过返回未封装的断言来 让杰斯特稍等一下。
或者,你可以将 async/await
与 .rejects
结合使用。
test('rejects to octopus', async () => {
await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus');
});
匹配器
.toBe(value)
使用 .toBe
比较原始值或检查对象实例的引用标识。 它调用 Object.is
来比较值,这比 ===
严格相等运算符更适合测试。
例如,此代码将验证 can
对象的一些属性:
const can = {
name: 'pamplemousse',
ounces: 12,
};
describe('the can', () => {
test('has 12 ounces', () => {
expect(can.ounces).toBe(12);
});
test('has a sophisticated name', () => {
expect(can.name).toBe('pamplemousse');
});
});
不要将 .toBe
与浮点数一起使用。 例如,由于四舍五入的原因,JavaScript 中的 0.2 + 0.1
并不严格等于 0.3
。 如果你有浮点数,请尝试使用 .toBeCloseTo
。
尽管 .toBe
匹配器 checks 引用同一性,但如果断言失败,它会对 reports 值进行深度比较。 如果属性之间的差异不能帮助你理解测试失败的原因,特别是在报告很大的情况下,那么你可以将比较移至 expect
函数中。 例如,断言元素是否是同一实例:
- 将
expect(received).toBe(expected)
重写为expect(Object.is(received, expected)).toBe(true)
- 将
expect(received).not.toBe(expected)
重写为expect(Object.is(received, expected)).toBe(false)
.toHaveBeenCalled()
也在别名下: .toBeCalled()
使用 .toHaveBeenCalledWith
确保使用特定参数调用模拟函数。 使用 .toEqual
使用的相同算法检查参数。
例如,假设你有一个 drinkAll(drink, flavour)
函数,它接受 drink
函数并将其应用于所有可用的饮料。 你可能想检查 drink
是否被称为 'lemon'
,但不是 'octopus'
,因为 'octopus'
的味道真的很奇怪,为什么会有章鱼味的东西? 你可以使用此测试套件来做到这一点:
function drinkAll(callback, flavour) {
if (flavour !== 'octopus') {
callback(flavour);
}
}
describe('drinkAll', () => {
test('drinks something lemon-flavoured', () => {
const drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavoured', () => {
const drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
.toHaveBeenCalledTimes(number)
也在别名下: .toBeCalledTimes(number)
使用 .toHaveBeenCalledTimes
确保模拟函数被调用的确切次数。
例如,假设你有一个 drinkEach(drink, Array<flavor>)
函数,它接受 drink
函数并将其应用于传递的饮料数组。 你可能想要检查 Drink 函数被调用的确切次数。 你可以使用此测试套件来做到这一点:
test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).toHaveBeenCalledTimes(2);
});
.toHaveBeenCalledWith(arg1, arg2, ...)
也在别名下: .toBeCalledWith()
使用 .toHaveBeenCalledWith
确保使用特定参数调用模拟函数。 使用 .toEqual
使用的相同算法检查参数。
例如,假设你可以使用 register
函数注册饮料,而 applyToAll(f)
应该将函数 f
应用于所有已注册的饮料。 为了确保它有效,你可以这样写:
test('registration applies correctly to orange La Croix', () => {
const beverage = new LaCroix('orange');
register(beverage);
const f = jest.fn();
applyToAll(f);
expect(f).toHaveBeenCalledWith(beverage);
});
.toHaveBeenLastCalledWith(arg1, arg2, ...)
也在别名下: .lastCalledWith(arg1, arg2, ...)
如果你有模拟函数,则可以使用 .toHaveBeenLastCalledWith
来测试上次调用它时使用的参数。 例如,假设你有一个 applyToAllFlavors(f)
函数,它将 f
应用于一堆风味,并且你希望确保当你调用它时,它操作的最后一个风味是 'mango'
。 你可以写:
test('applying to all flavors does mango last', () => {
const drink = jest.fn();
applyToAllFlavors(drink);
expect(drink).toHaveBeenLastCalledWith('mango');
});
.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)
也在别名下: .nthCalledWith(nthCall, arg1, arg2, ...)
如果你有模拟函数,则可以使用 .toHaveBeenNthCalledWith
来测试第 n 次调用它的参数。 例如,假设你有一个 drinkEach(drink, Array<flavor>)
函数,它将 f
应用于一堆风味,并且你希望确保在调用它时,它操作的第一个风味是 'lemon'
,第二个是 'octopus'
。 你可以写:
test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).toHaveBeenNthCalledWith(1, 'lemon');
expect(drink).toHaveBeenNthCalledWith(2, 'octopus');
});
第 n 个参数必须是从 1 开始的正整数。
.toHaveReturned()
也在别名下: .toReturn()
如果你有一个模拟函数,你可以使用 .toHaveReturned
来测试该模拟函数是否至少成功返回一次(即,没有抛出错误)。 例如,假设你有一个返回 true
的模拟 drink
。 你可以写:
test('drinks returns', () => {
const drink = jest.fn(() => true);
drink();
expect(drink).toHaveReturned();
});
.toHaveReturnedTimes(number)
也在别名下: .toReturnTimes(number)
使用 .toHaveReturnedTimes
确保模拟函数成功返回(即,没有抛出错误)准确的次数。 任何抛出错误的模拟函数调用都不会计入该函数返回的次数。
例如,假设你有一个返回 true
的模拟 drink
。 你可以写:
test('drink returns twice', () => {
const drink = jest.fn(() => true);
drink();
drink();
expect(drink).toHaveReturnedTimes(2);
});
.toHaveReturnedWith(value)
也在别名下: .toReturnWith(value)
使用 .toHaveReturnedWith
确保模拟函数返回特定值。
例如,假设你有一个模拟 drink
,它返回所喝饮料的名称。 你可以写:
test('drink returns La Croix', () => {
const beverage = {name: 'La Croix'};
const drink = jest.fn(beverage => beverage.name);
drink(beverage);
expect(drink).toHaveReturnedWith('La Croix');
});
.toHaveLastReturnedWith(value)
也在别名下: .lastReturnedWith(value)
使用 .toHaveLastReturnedWith
测试模拟函数最后返回的具体值。 如果对模拟函数的最后一次调用引发了错误,则无论你提供什么值作为预期返回值,此匹配器都将失败。
例如,假设你有一个模拟 drink
,它返回所喝饮料的名称。 你可以写:
test('drink returns La Croix (Orange) last', () => {
const beverage1 = {name: 'La Croix (Lemon)'};
const beverage2 = {name: 'La Croix (Orange)'};
const drink = jest.fn(beverage => beverage.name);
drink(beverage1);
drink(beverage2);
expect(drink).toHaveLastReturnedWith('La Croix (Orange)');
});
.toHaveNthReturnedWith(nthCall, value)
也在别名下: .nthReturnedWith(nthCall, value)
使用 .toHaveNthReturnedWith
测试模拟函数第 n 次调用返回的特定值。 如果对模拟函数的第 n 次调用抛出错误,则无论你提供什么值作为预期返回值,此匹配器都将失败。
例如,假设你有一个模拟 drink
,它返回所喝饮料的名称。 你可以写:
test('drink returns expected nth calls', () => {
const beverage1 = {name: 'La Croix (Lemon)'};
const beverage2 = {name: 'La Croix (Orange)'};
const drink = jest.fn(beverage => beverage.name);
drink(beverage1);
drink(beverage2);
expect(drink).toHaveNthReturnedWith(1, 'La Croix (Lemon)');
expect(drink).toHaveNthReturnedWith(2, 'La Croix (Orange)');
});
第 n 个参数必须是从 1 开始的正整数。
.toHaveLength(number)
使用 .toHaveLength
检查对象是否具有 .length
属性并且该属性已设置为某个数值。
这对于检查数组或字符串大小特别有用。
expect([1, 2, 3]).toHaveLength(3);
expect('abc').toHaveLength(3);
expect('').not.toHaveLength(5);
.toHaveProperty(keyPath, value?)
使用 .toHaveProperty
检查所提供的引用 keyPath
处的属性是否存在于某个对象。 要检查对象中的深度嵌套属性,你可以使用 点符号 或包含深度引用的 keyPath 的数组。
你可以提供可选的 value
参数来比较接收到的属性值(递归地比较对象实例的所有属性,也称为深度相等,如 toEqual
匹配器)。
以下示例包含具有嵌套属性的 houseForSale
对象。 我们使用 toHaveProperty
来检查对象中各种属性是否存在及其值。
// Object containing house features to be tested
const houseForSale = {
bath: true,
bedrooms: 4,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
area: 20,
wallColor: 'white',
'nice.oven': true,
},
livingroom: {
amenities: [
{
couch: [
['large', {dimensions: [20, 20]}],
['small', {dimensions: [10, 10]}],
],
},
],
},
'ceiling.height': 2,
};
test('this house has my desired features', () => {
// Example Referencing
expect(houseForSale).toHaveProperty('bath');
expect(houseForSale).toHaveProperty('bedrooms', 4);
expect(houseForSale).not.toHaveProperty('pool');
// Deep referencing using dot notation
expect(houseForSale).toHaveProperty('kitchen.area', 20);
expect(houseForSale).toHaveProperty('kitchen.amenities', [
'oven',
'stove',
'washer',
]);
expect(houseForSale).not.toHaveProperty('kitchen.open');
// Deep referencing using an array containing the keyPath
expect(houseForSale).toHaveProperty(['kitchen', 'area'], 20);
expect(houseForSale).toHaveProperty(
['kitchen', 'amenities'],
['oven', 'stove', 'washer'],
);
expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven');
expect(houseForSale).toHaveProperty(
'livingroom.amenities[0].couch[0][1].dimensions[0]',
20,
);
expect(houseForSale).toHaveProperty(['kitchen', 'nice.oven']);
expect(houseForSale).not.toHaveProperty(['kitchen', 'open']);
// Referencing keys with dot in the key itself
expect(houseForSale).toHaveProperty(['ceiling.height'], 'tall');
});
.toBeCloseTo(number, numDigits?)
使用 toBeCloseTo
比较浮点数是否近似相等。
可选的 numDigits
参数限制检查 after 小数点的位数。 对于默认值 2
,测试标准为 Math.abs(expected - received) < 0.005
(即 10 ** -2 / 2
)。
直观的相等比较通常会失败,因为十进制(以 10 为基数)值的算术在有限精度的二进制(以 2 为基数)表示中通常会出现舍入误差。 例如,此测试失败:
test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBe(0.3); // Fails!
});
它失败了,因为在 JavaScript 中,0.2 + 0.1
实际上是 0.30000000000000004
。
例如,此测试以 5 位精度通过:
test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
});
因为浮点错误是 toBeCloseTo
解决的问题,所以它不支持大整数值。
.toBeDefined()
使用 .toBeDefined
检查变量是否未定义。 例如,如果你想检查函数 fetchNewFlavorIdea()
是否返回某些内容,你可以编写:
test('there is a new flavor idea', () => {
expect(fetchNewFlavorIdea()).toBeDefined();
});
你可以编写 expect(fetchNewFlavorIdea()).not.toBe(undefined)
,但更好的做法是避免在代码中直接引用 undefined
。
.toBeFalsy()
当你不关心值是什么并且希望确保布尔上下文中的值为 false 时,请使用 .toBeFalsy
。 例如,假设你有一些如下所示的应用代码:
drinkSomeLaCroix();
if (!getErrors()) {
drinkMoreLaCroix();
}
你可能不关心 getErrors
返回什么,具体来说 - 它可能返回 false
、null
或 0
,并且你的代码仍然可以工作。 所以如果你想测试喝了一些 La Croix 后没有错误,你可以这样写:
test('drinking La Croix does not lead to errors', () => {
drinkSomeLaCroix();
expect(getErrors()).toBeFalsy();
});
在 JavaScript 中,有六个假值: false
、0
、''
、null
、undefined
和 NaN
。 其他一切都是真实的。
.toBeGreaterThan(number | bigint)
使用 toBeGreaterThan
比较 received > expected
的数字或大整数值。 例如,测试 ouncesPerCan()
返回的值是否大于 10 盎司:
test('ounces per can is more than 10', () => {
expect(ouncesPerCan()).toBeGreaterThan(10);
});
.toBeGreaterThanOrEqual(number | bigint)
使用 toBeGreaterThanOrEqual
比较 received >= expected
的数字或大整数值。 例如,测试 ouncesPerCan()
返回的值至少为 12 盎司:
test('ounces per can is at least 12', () => {
expect(ouncesPerCan()).toBeGreaterThanOrEqual(12);
});
.toBeLessThan(number | bigint)
使用 toBeLessThan
比较 received < expected
的数字或大整数值。 例如,测试 ouncesPerCan()
返回的值是否小于 20 盎司:
test('ounces per can is less than 20', () => {
expect(ouncesPerCan()).toBeLessThan(20);
});
.toBeLessThanOrEqual(number | bigint)
使用 toBeLessThanOrEqual
比较 received <= expected
的数字或大整数值。 例如,测试 ouncesPerCan()
返回的值最多为 12 盎司:
test('ounces per can is at most 12', () => {
expect(ouncesPerCan()).toBeLessThanOrEqual(12);
});
.toBeInstanceOf(Class)
使用 .toBeInstanceOf(Class)
检查对象是否是类的实例。 该匹配器在下面使用 instanceof
。
class A {}
expect(new A()).toBeInstanceOf(A);
expect(() => {}).toBeInstanceOf(Function);
expect(new A()).toBeInstanceOf(Function); // throws
.toBeNull()
.toBeNull()
与 .toBe(null)
相同,但错误消息更好一些。 因此,当你想检查某些内容是否为空时,请使用 .toBeNull()
。
function bloop() {
return null;
}
test('bloop returns null', () => {
expect(bloop()).toBeNull();
});
.toBeTruthy()
当你不关心值是什么并且希望确保值在布尔上下文中为 true 时,请使用 .toBeTruthy
。 例如,假设你有一些如下所示的应用代码:
drinkSomeLaCroix();
if (thirstInfo()) {
drinkMoreLaCroix();
}
你可能不关心 thirstInfo
返回什么,具体来说 - 它可能返回 true
或复杂的对象,并且你的代码仍然可以工作。 因此,如果你想在喝了一些 La Croix 后测试 thirstInfo
是否真实,你可以这样写:
test('drinking La Croix leads to having thirst info', () => {
drinkSomeLaCroix();
expect(thirstInfo()).toBeTruthy();
});
在 JavaScript 中,有六个假值: false
、0
、''
、null
、undefined
和 NaN
。 其他一切都是真实的。
.toBeUndefined()
使用 .toBeUndefined
检查变量是否未定义。 例如,如果你想检查函数 bestDrinkForFlavor(flavor)
是否为 'octopus'
口味返回 undefined
,因为没有好的章鱼味饮料:
test('the best drink for octopus flavor is undefined', () => {
expect(bestDrinkForFlavor('octopus')).toBeUndefined();
});
你可以编写 expect(bestDrinkForFlavor('octopus')).toBe(undefined)
,但更好的做法是避免在代码中直接引用 undefined
。
.toBeNaN()
当检查值是 NaN
时使用 .toBeNaN
。
test('passes when value is NaN', () => {
expect(NaN).toBeNaN();
expect(1).not.toBeNaN();
});
.toContain(item)
当你想要检查某个项目是否在数组中时,请使用 .toContain
。 为了测试数组中的项目,这使用了 ===
,即严格的相等检查。 .toContain
还可以检查一个字符串是否是另一个字符串的子字符串。
例如,如果 getAllFlavors()
返回一个口味数组,并且你想确保 lime
位于其中,则可以编写:
test('the flavor list contains lime', () => {
expect(getAllFlavors()).toContain('lime');
});
该匹配器还接受其他可迭代对象,例如字符串、集合、节点列表和 HTML 集合。
.toContainEqual(item)
当你想要检查数组中是否包含具有特定结构和值的项目时,请使用 .toContainEqual
。 为了测试数组中的项目,此匹配器递归地检查所有字段的相等性,而不是检查对象标识。
describe('my beverage', () => {
test('is delicious and not sour', () => {
const myBeverage = {delicious: true, sour: false};
expect(myBeverages()).toContainEqual(myBeverage);
});
});
.toEqual(value)
使用 .toEqual
递归比较对象实例的所有属性(也称为 "deep" 相等性)。 它调用 Object.is
来比较原始值,这比 ===
严格相等运算符更适合测试。
例如,.toEqual
和 .toBe
在此测试套件中的行为不同,因此所有测试都通过:
const can1 = {
flavor: 'grapefruit',
ounces: 12,
};
const can2 = {
flavor: 'grapefruit',
ounces: 12,
};
describe('the La Croix cans on my desk', () => {
test('have all the same properties', () => {
expect(can1).toEqual(can2);
});
test('are not the exact same can', () => {
expect(can1).not.toBe(can2);
});
});
toEqual
忽略具有 undefined
属性、undefined
数组项、数组稀疏性或对象类型不匹配的对象键。 要考虑这些,请改用 .toStrictEqual
。
.toEqual
不会对两个错误执行深度相等检查。 仅错误的 message
属性被视为相等。 建议使用 .toThrow
匹配器来测试错误。
如果属性之间的差异不能帮助你理解测试失败的原因,特别是在报告很大的情况下,那么你可以将比较移至 expect
函数中。 例如,使用 Buffer
类的 equals
方法来断言缓冲区是否包含相同的内容:
- 将
expect(received).toEqual(expected)
重写为expect(received.equals(expected)).toBe(true)
- 将
expect(received).not.toEqual(expected)
重写为expect(received.equals(expected)).toBe(false)
.toMatch(regexp | string)
使用 .toMatch
检查字符串是否与正则表达式匹配。
例如,你可能不知道 essayOnTheBestFlavor()
到底返回什么,但你知道它是一个非常长的字符串,并且子字符串 grapefruit
应该在其中的某个位置。 你可以使用以下方法进行测试:
describe('an essay on the best flavor', () => {
test('mentions grapefruit', () => {
expect(essayOnTheBestFlavor()).toMatch(/grapefruit/);
expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit'));
});
});
该匹配器还接受一个字符串,它将尝试匹配该字符串:
describe('grapefruits are healthy', () => {
test('grapefruits are a fruit', () => {
expect('grapefruits').toMatch('fruit');
});
});
.toMatchObject(object)
使用 .toMatchObject
检查 JavaScript 对象是否与对象属性的子集匹配。 它将匹配接收到的对象,其属性在预期对象中为 not。
你还可以传递对象数组,在这种情况下,仅当接收到的数组中的每个对象与预期数组中的相应对象匹配(在上述 toMatchObject
意义上)时,该方法才会返回 true。 如果你想检查两个数组的元素数量是否匹配,这非常有用,这与 arrayContaining
不同,后者允许在接收的数组中包含额外的元素。
你可以将属性与值或匹配器进行匹配。
const houseForSale = {
bath: true,
bedrooms: 4,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
area: 20,
wallColor: 'white',
},
};
const desiredHouse = {
bath: true,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
wallColor: expect.stringMatching(/white|yellow/),
},
};
test('the house has my desired features', () => {
expect(houseForSale).toMatchObject(desiredHouse);
});
describe('toMatchObject applied to arrays', () => {
test('the number of elements must match exactly', () => {
expect([{foo: 'bar'}, {baz: 1}]).toMatchObject([{foo: 'bar'}, {baz: 1}]);
});
test('.toMatchObject is called for each elements, so extra object properties are okay', () => {
expect([{foo: 'bar'}, {baz: 1, extra: 'quux'}]).toMatchObject([
{foo: 'bar'},
{baz: 1},
]);
});
});
.toMatchSnapshot(propertyMatchers?, hint?)
这可确保值与最新快照匹配。 查看 快照测试指南 了解更多信息。
你可以提供可选的 propertyMatchers
对象参数,该参数具有非对称匹配器作为预期属性子集的值,if 接收到的值将是 object 实例。 它就像 toMatchObject
,对属性子集具有灵活的标准,然后进行快照测试作为其余属性的精确标准。
你可以提供附加到测试名称的可选 hint
字符串参数。 尽管 Jest 总是在快照名称末尾附加一个数字,但简短的描述性提示可能比数字更有用,可以区分 single it
或 test
块中的 multiple 快照。 Jest 按相应 .snap
文件中的名称对快照进行排序。
.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)
确保值与最新快照匹配。
你可以提供可选的 propertyMatchers
对象参数,该参数具有非对称匹配器作为预期属性子集的值,if 接收到的值将是 object 实例。 它就像 toMatchObject
,对属性子集具有灵活的标准,然后进行快照测试作为其余属性的精确标准。
第一次运行测试时,Jest 将 inlineSnapshot
字符串参数添加到测试文件中的匹配器(而不是外部 .snap
文件)。
查看 内联快照 部分以获取更多信息。
.toStrictEqual(value)
使用 .toStrictEqual
来测试对象是否具有相同的结构和类型。
与 .toEqual
的区别:
- 检查具有
undefined
属性的键,例如{a: undefined, b: 2}
不会等于{b: 2}
; - 考虑了
undefined
个项目,例如[2]
不会等于[2, undefined]
; - 检查数组稀疏性,例如
[, 1]
不会等于[undefined, 1]
; - 检查对象类型,例如 具有字段
a
和b
的类实例不等于具有字段a
和b
的字面量对象。
class LaCroix {
constructor(flavor) {
this.flavor = flavor;
}
}
describe('the La Croix cans on my desk', () => {
test('are not semantically the same', () => {
expect(new LaCroix('lemon')).toEqual({flavor: 'lemon'});
expect(new LaCroix('lemon')).not.toStrictEqual({flavor: 'lemon'});
});
});
.toThrow(error?)
也在别名下: .toThrowError(error?)
使用 .toThrow
来测试函数在调用时是否抛出异常。 例如,如果我们想测试 drinkFlavor('octopus')
抛出异常,因为章鱼味太恶心了,不能喝,我们可以这样写:
test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow();
});
你必须将代码封装在函数中,否则错误将不会被捕获并且断言将失败。
你可以提供一个可选参数来测试是否引发特定错误:
- 正则表达式: 错误消息 matches 模式
- string: 错误消息 includes 子字符串
- 错误对象: 错误消息是 等于 对象的消息属性
- 错误类别: 错误对象是 的实例 类
例如,假设 drinkFlavor
的编码如下:
function drinkFlavor(flavor) {
if (flavor == 'octopus') {
throw new DisgustingFlavorError('yuck, octopus flavor');
}
// Do some other stuff
}
我们可以通过多种方式测试此错误的抛出情况:
test('throws on octopus', () => {
function drinkOctopus() {
drinkFlavor('octopus');
}
// Test that the error message says "yuck" somewhere: these are equivalent
expect(drinkOctopus).toThrow(/yuck/);
expect(drinkOctopus).toThrow('yuck');
// Test the exact error message
expect(drinkOctopus).toThrow(/^yuck, octopus flavor$/);
expect(drinkOctopus).toThrow(new Error('yuck, octopus flavor'));
// Test that we get a DisgustingFlavorError
expect(drinkOctopus).toThrow(DisgustingFlavorError);
});
.toThrowErrorMatchingSnapshot(hint?)
使用 .toThrowErrorMatchingSnapshot
测试函数在调用时是否抛出与最新快照匹配的错误。
你可以提供附加到测试名称的可选 hint
字符串参数。 尽管 Jest 总是在快照名称末尾附加一个数字,但简短的描述性提示可能比数字更有用,可以区分 single it
或 test
块中的 multiple 快照。 Jest 按相应 .snap
文件中的名称对快照进行排序。
例如,假设你有一个 drinkFlavor
函数,每当风味为 'octopus'
时就会抛出异常,代码如下:
function drinkFlavor(flavor) {
if (flavor == 'octopus') {
throw new DisgustingFlavorError('yuck, octopus flavor');
}
// Do some other stuff
}
该函数的测试将如下所示:
test('throws on octopus', () => {
function drinkOctopus() {
drinkFlavor('octopus');
}
expect(drinkOctopus).toThrowErrorMatchingSnapshot();
});
它将生成以下快照:
exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;
查看 React 树快照测试 了解有关快照测试的更多信息。
.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)
使用 .toThrowErrorMatchingInlineSnapshot
测试函数在调用时是否抛出与最新快照匹配的错误。
第一次运行测试时,Jest 将 inlineSnapshot
字符串参数添加到测试文件中的匹配器(而不是外部 .snap
文件)。
查看 内联快照 部分以获取更多信息。
不对称匹配器
expect.anything()
expect.anything()
匹配除 null
或 undefined
之外的任何内容。 你可以在 toEqual
或 toBeCalledWith
中使用它而不是字面量值。 例如,如果你想检查是否使用非空参数调用模拟函数:
test('map calls its argument with a non-null argument', () => {
const mock = jest.fn();
[1].map(x => mock(x));
expect(mock).toHaveBeenCalledWith(expect.anything());
});
expect.any(constructor)
expect.any(constructor)
匹配使用给定构造函数创建的任何内容,或者它是传递类型的原语。 你可以在 toEqual
或 toBeCalledWith
中使用它而不是字面量值。 例如,如果你想检查是否使用数字调用模拟函数:
class Cat {}
function getCat(fn) {
return fn(new Cat());
}
test('randocall calls its callback with a class instance', () => {
const mock = jest.fn();
getCat(mock);
expect(mock).toHaveBeenCalledWith(expect.any(Cat));
});
function randocall(fn) {
return fn(Math.floor(Math.random() * 6 + 1));
}
test('randocall calls its callback with a number', () => {
const mock = jest.fn();
randocall(mock);
expect(mock).toHaveBeenCalledWith(expect.any(Number));
});
expect.arrayContaining(array)
expect.arrayContaining(array)
匹配接收到的数组,其中包含预期数组中的所有元素。 也就是说,预期数组是接收数组的 subset。 因此,它匹配接收到的数组,该数组包含预期数组中的 not 元素。
你可以使用它来代替字面量值:
- 在
toEqual
或toBeCalledWith
- 匹配
objectContaining
或toMatchObject
中的属性
describe('arrayContaining', () => {
const expected = ['Alice', 'Bob'];
it('matches even if received contains additional elements', () => {
expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected));
});
it('does not match if received does not contain expected elements', () => {
expect(['Bob', 'Eve']).not.toEqual(expect.arrayContaining(expected));
});
});
describe('Beware of a misunderstanding! A sequence of dice rolls', () => {
const expected = [1, 2, 3, 4, 5, 6];
it('matches even with an unexpected number 7', () => {
expect([4, 1, 6, 7, 3, 5, 2, 5, 4, 6]).toEqual(
expect.arrayContaining(expected),
);
});
it('does not match without an expected number 2', () => {
expect([4, 1, 6, 7, 3, 5, 7, 5, 4, 6]).not.toEqual(
expect.arrayContaining(expected),
);
});
});
expect.not.arrayContaining(array)
expect.not.arrayContaining(array)
匹配接收到的数组,该数组不包含预期数组中的所有元素。 即接收数组的预期数组 不是子集。
它是 expect.arrayContaining
的倒数。
describe('not.arrayContaining', () => {
const expected = ['Samantha'];
it('matches if the actual array does not contain the expected elements', () => {
expect(['Alice', 'Bob', 'Eve']).toEqual(
expect.not.arrayContaining(expected),
);
});
});
expect.closeTo(number, numDigits?)
expect.closeTo(number, numDigits?)
在比较对象属性或数组项中的浮点数时很有用。 如果需要比较数字,请使用 .toBeCloseTo
。
可选的 numDigits
参数限制检查 after 小数点的位数。 对于默认值 2
,测试标准为 Math.abs(expected - received) < 0.005 (that is, 10 ** -2 / 2)
。
例如,此测试以 5 位精度通过:
test('compare float in object properties', () => {
expect({
title: '0.1 + 0.2',
sum: 0.1 + 0.2,
}).toEqual({
title: '0.1 + 0.2',
sum: expect.closeTo(0.3, 5),
});
});
expect.objectContaining(object)
expect.objectContaining(object)
匹配任何递归匹配预期属性的接收对象。 即,预期对象是接收到的对象的 subset。 因此,它匹配接收到的对象,该对象包含预期对象中的 存在 属性。
你可以使用匹配器、expect.anything()
等,而不是预期对象中的字面量属性值。
例如,假设我们希望使用 Event
对象调用 onPress
函数,而我们需要验证的是该事件具有 event.x
和 event.y
属性。 我们可以通过以下方式做到这一点:
test('onPress gets called with the right thing', () => {
const onPress = jest.fn();
simulatePresses(onPress);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({
x: expect.any(Number),
y: expect.any(Number),
}),
);
});
expect.not.objectContaining(object)
expect.not.objectContaining(object)
匹配任何未递归匹配预期属性的接收对象。 即,接收到的对象的期望对象 不是子集。 因此,它匹配接收到的对象,该对象包含预期对象中的 not 属性。
它是 expect.objectContaining
的倒数。
describe('not.objectContaining', () => {
const expected = {foo: 'bar'};
it('matches if the actual object does not contain expected key: value pairs', () => {
expect({bar: 'baz'}).toEqual(expect.not.objectContaining(expected));
});
});
expect.stringContaining(string)
如果 expect.stringContaining(string)
是包含确切预期字符串的字符串,则与接收到的值匹配。
expect.not.stringContaining(string)
如果 expect.not.stringContaining(string)
不是字符串或者不包含确切的预期字符串,则 expect.not.stringContaining(string)
与接收到的值匹配。
它是 expect.stringContaining
的倒数。
describe('not.stringContaining', () => {
const expected = 'Hello world!';
it('matches if the received value does not contain the expected substring', () => {
expect('How are you?').toEqual(expect.not.stringContaining(expected));
});
});
expect.stringMatching(string | regexp)
如果 expect.stringMatching(string | regexp)
是与预期字符串或正则表达式匹配的字符串,则与接收到的值匹配。
你可以使用它来代替字面量值:
- 在
toEqual
或toBeCalledWith
- 匹配
arrayContaining
中的元素 - 匹配
objectContaining
或toMatchObject
中的属性
此示例还展示了如何嵌套多个不对称匹配器,其中 expect.stringMatching
位于 expect.arrayContaining
内。
describe('stringMatching in arrayContaining', () => {
const expected = [
expect.stringMatching(/^Alic/),
expect.stringMatching(/^[BR]ob/),
];
it('matches even if received contains additional elements', () => {
expect(['Alicia', 'Roberto', 'Evelina']).toEqual(
expect.arrayContaining(expected),
);
});
it('does not match if received does not contain expected elements', () => {
expect(['Roberto', 'Evelina']).not.toEqual(
expect.arrayContaining(expected),
);
});
});
expect.not.stringMatching(string | regexp)
如果 expect.not.stringMatching(string | regexp)
不是字符串或者是与预期字符串或正则表达式不匹配的字符串,则 expect.not.stringMatching(string | regexp)
与接收到的值匹配。
它是 expect.stringMatching
的倒数。
describe('not.stringMatching', () => {
const expected = /Hello world!/;
it('matches if the received value does not match the expected regex', () => {
expect('How are you?').toEqual(expect.not.stringMatching(expected));
});
});
断言计数
expect.assertions(number)
expect.assertions(number)
验证测试期间是否调用了一定数量的断言。 这在测试异步代码时通常很有用,以确保回调中的断言确实被调用。
例如,假设我们有一个函数 doAsync
,它接收两个回调 callback1
和 callback2
,它将以未知的顺序异步调用这两个回调。 我们可以用以下方法进行测试:
test('doAsync calls both callbacks', () => {
expect.assertions(2);
function callback1(data) {
expect(data).toBeTruthy();
}
function callback2(data) {
expect(data).toBeTruthy();
}
doAsync(callback1, callback2);
});
expect.assertions(2)
调用确保两个回调实际上都被调用。
expect.hasAssertions()
expect.hasAssertions()
验证在测试期间至少调用了一个断言。 这在测试异步代码时通常很有用,以确保回调中的断言确实被调用。
例如,假设我们有一些处理状态的函数。 prepareState
使用状态对象调用回调,validateState
在该状态对象上运行,waitOnState
返回一个等待所有 prepareState
回调完成的 promise。 我们可以用以下方法进行测试:
test('prepareState prepares a valid state', () => {
expect.hasAssertions();
prepareState(state => {
expect(validateState(state)).toBeTruthy();
});
return waitOnState();
});
expect.hasAssertions()
调用确保 prepareState
回调实际上被调用。
扩展实用程序
expect.addEqualityTesters(testers)
你可以使用 expect.addEqualityTesters
添加自己的方法来测试两个对象是否相等。 例如,假设你的代码中有一个表示体积的类,并且可以确定使用不同单位的两个体积是否相等。 你可能希望 toEqual
(和其他相等匹配器)在与 Volume 类进行比较时使用此自定义相等方法。 你可以添加自定义相等测试器,以便在比较 Volume 类时让 toEqual
检测并应用自定义逻辑:
- JavaScript
- TypeScript
// For simplicity in this example, we'll just support the units 'L' and 'mL'
export class Volume {
constructor(amount, unit) {
this.amount = amount;
this.unit = unit;
}
toString() {
return `[Volume ${this.amount}${this.unit}]`;
}
equals(other) {
if (this.unit === other.unit) {
return this.amount === other.amount;
} else if (this.unit === 'L' && other.unit === 'mL') {
return this.amount * 1000 === other.unit;
} else {
return this.amount === other.unit * 1000;
}
}
}
import {expect} from '@jest/globals';
import {Volume} from './Volume.js';
function areVolumesEqual(a, b) {
const isAVolume = a instanceof Volume;
const isBVolume = b instanceof Volume;
if (isAVolume && isBVolume) {
return a.equals(b);
} else if (isAVolume === isBVolume) {
return undefined;
} else {
return false;
}
}
expect.addEqualityTesters([areVolumesEqual]);
import {expect, test} from '@jest/globals';
import {Volume} from '../Volume.js';
import '../areVolumesEqual.js';
test('are equal with different units', () => {
expect(new Volume(1, 'L')).toEqual(new Volume(1000, 'mL'));
});
// For simplicity in this example, we'll just support the units 'L' and 'mL'
export class Volume {
public amount: number;
public unit: 'L' | 'mL';
constructor(amount: number, unit: 'L' | 'mL') {
this.amount = amount;
this.unit = unit;
}
toString(): string {
return `[Volume ${this.amount}${this.unit}]`;
}
equals(other: Volume): boolean {
if (this.unit === other.unit) {
return this.amount === other.amount;
} else if (this.unit === 'L' && other.unit === 'mL') {
return this.amount * 1000 === other.amount;
} else {
return this.amount === other.amount * 1000;
}
}
}
import {expect} from '@jest/globals';
import {Volume} from './Volume.js';
function areVolumesEqual(a: unknown, b: unknown): boolean | undefined {
const isAVolume = a instanceof Volume;
const isBVolume = b instanceof Volume;
if (isAVolume && isBVolume) {
return a.equals(b);
} else if (isAVolume === isBVolume) {
return undefined;
} else {
return false;
}
}
expect.addEqualityTesters([areVolumesEqual]);
import {expect, test} from '@jest/globals';
import {Volume} from '../Volume.js';
import '../areVolumesEqual.js';
test('are equal with different units', () => {
expect(new Volume(1, 'L')).toEqual(new Volume(1000, 'mL'));
});
自定义相等测试器 API
自定义测试器是返回比较两个给定参数相等性的结果(true
或 false
)的函数,或者如果测试器不处理给定对象并希望将相等性委托给其他测试器(例如,内置相等性测试器),则返回 undefined
)。
使用 3 个参数调用自定义测试器: 要比较的两个对象和自定义测试器数组(用于递归测试器,请参阅下面的部分)。
这些辅助函数和属性可以在自定义测试器内的 this
上找到:
this.equals(a, b, customTesters?)
这是一个深度相等函数,如果两个对象具有相同的值(递归地),它将返回 true
。 它可以选择使用自定义相等性测试人员列表来应用深度相等性检查。 如果你使用此功能,请传递给你的测试人员的自定义测试人员,以便进一步应用 equals
的相等性检查也可以使用测试作者可能已配置的自定义测试人员。 有关更多详细信息,请参阅 递归自定义相等测试器 部分中的示例。
匹配者与测试者
匹配器是 expect
上可用的方法,例如 expect().toEqual()
。 toEqual
是匹配器。 测试器是匹配器使用的一种方法,用于进行相等性检查以确定对象是否相同。
当你想要提供测试作者可以在其测试中使用的自定义断言时,可以很好地使用自定义匹配器。 例如,expect.extend
部分中的 toBeWithinRange
示例就是自定义匹配器的一个很好的示例。 有时,测试作者可能想要断言两个数字完全相等,并且应该使用 toBe
。 然而,其他时候,测试作者可能希望在测试中允许一定的灵活性,并且 toBeWithinRange
可能是更合适的断言。
自定义相等测试器非常适合全局扩展 Jest 匹配器,以将自定义相等逻辑应用于所有相等比较。 测试作者无法为某些断言打开自定义测试器,并为其他断言关闭它们(如果需要这种行为,则应使用自定义匹配器)。 例如,定义如何检查所有匹配器的两个 Volume
对象是否相等将是一个很好的自定义相等测试器。
递归自定义相等测试器
如果你的自定义相等测试器正在测试具有你想要进行深度相等的属性的对象,则应使用可供相等测试器使用的 this.equals
辅助程序。 此 equals
方法与 Jest 内部用于所有深度相等比较的深度等于方法相同。 它是调用自定义相等测试器的方法。 它接受一组自定义相等测试器作为第三个参数。 还为自定义相等测试器提供了一组自定义测试器作为其第三个参数。 将此参数传递到 equals
的第三个参数中,以便对对象进行更深入的任何进一步的相等性检查也可以利用自定义相等性测试器。
例如,假设你有一个 Book
类,其中包含 Author
类的数组,并且这两个类都有自定义测试器。 Book
自定义测试器想要对 Author
的数组进行深度相等性检查,并传递给它的自定义测试器,因此应用 Author
自定义相等性测试器:
function areAuthorEqual(a, b) {
const isAAuthor = a instanceof Author;
const isBAuthor = b instanceof Author;
if (isAAuthor && isBAuthor) {
// Authors are equal if they have the same name
return a.name === b.name;
} else if (isAAuthor === isBAuthor) {
return undefined;
} else {
return false;
}
}
function areBooksEqual(a, b, customTesters) {
const isABook = a instanceof Book;
const isBBook = b instanceof Book;
if (isABook && isBBook) {
// Books are the same if they have the same name and author array. We need
// to pass customTesters to equals here so the Author custom tester will be
// used when comparing Authors
return (
a.name === b.name && this.equals(a.authors, b.authors, customTesters)
);
} else if (isABook === isBBook) {
return undefined;
} else {
return false;
}
}
expect.addEqualityTesters([areAuthorsEqual, areBooksEqual]);
请记住将相等测试器定义为常规函数和 not 箭头函数,以便访问测试器上下文辅助程序(例如 this.equals
)。
expect.addSnapshotSerializer(serializer)
你可以调用 expect.addSnapshotSerializer
添加格式化特定于应用的数据结构的模块。
对于单个测试文件,添加的模块位于 snapshotSerializers
配置中的任何模块之前,这些模块位于内置 JavaScript 类型和 React 元素的默认快照序列化器之前。 最后添加的模块是第一个测试的模块。
import serializer from 'my-serializer-module';
expect.addSnapshotSerializer(serializer);
// affects expect(value).toMatchSnapshot() assertions in the test file
如果你在各个测试文件中添加快照序列化器而不是将其添加到 snapshotSerializers
配置中:
- 你使依赖显式而不是隐式。
- 你可以避免可能导致你从 create-react-app 弹出的配置限制。
请参阅 配置 Jest 了解更多信息。
expect.extend(matchers)
你可以使用 expect.extend
将你自己的匹配器添加到 Jest。 例如,假设你正在测试一个数字实用程序库,并且经常断言数字出现在其他数字的特定范围内。 你可以将其抽象为 toBeWithinRange
匹配器:
- JavaScript
- TypeScript
import {expect} from '@jest/globals';
function toBeWithinRange(actual, floor, ceiling) {
if (
typeof actual !== 'number' ||
typeof floor !== 'number' ||
typeof ceiling !== 'number'
) {
throw new Error('These must be of type number!');
}
const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} not to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: false,
};
}
}
expect.extend({
toBeWithinRange,
});
import {expect, test} from '@jest/globals';
import '../toBeWithinRange';
test('is within range', () => expect(100).toBeWithinRange(90, 110));
test('is NOT within range', () => expect(101).not.toBeWithinRange(0, 100));
test('asymmetric ranges', () => {
expect({apples: 6, bananas: 3}).toEqual({
apples: expect.toBeWithinRange(1, 10),
bananas: expect.not.toBeWithinRange(11, 20),
});
});
// optionally add a type declaration, e.g. it enables autocompletion in IDEs
declare module 'expect' {
interface AsymmetricMatchers {
toBeWithinRange(floor: number, ceiling: number): void;
}
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
export {};
import {expect} from '@jest/globals';
import type {MatcherFunction} from 'expect';
const toBeWithinRange: MatcherFunction<[floor: unknown, ceiling: unknown]> =
// `floor` and `ceiling` get types from the line above
// it is recommended to type them as `unknown` and to validate the values
function (actual, floor, ceiling) {
if (
typeof actual !== 'number' ||
typeof floor !== 'number' ||
typeof ceiling !== 'number'
) {
throw new Error('These must be of type number!');
}
const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
message: () =>
// `this` context will have correct typings
`expected ${this.utils.printReceived(
actual,
)} not to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: false,
};
}
};
expect.extend({
toBeWithinRange,
});
declare module 'expect' {
interface AsymmetricMatchers {
toBeWithinRange(floor: number, ceiling: number): void;
}
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
import {expect, test} from '@jest/globals';
import '../toBeWithinRange';
test('is within range', () => expect(100).toBeWithinRange(90, 110));
test('is NOT within range', () => expect(101).not.toBeWithinRange(0, 100));
test('asymmetric ranges', () => {
expect({apples: 6, bananas: 3}).toEqual({
apples: expect.toBeWithinRange(1, 10),
bananas: expect.not.toBeWithinRange(11, 20),
});
});
匹配器的类型声明可以存在于 .d.ts
文件中或导入的 .ts
模块中(分别参见上面的 JS 和 TS 示例)。 如果将声明保留在 .d.ts
文件中,请确保它包含在程序中并且是有效的模块,即它至少有一个空的 export {}
。
你可以通过将 expect.extend
调用移至 setupFilesAfterEnv
脚本来为所有测试启用匹配器,而不是将 toBeWithinRange
模块导入到测试文件中:
import {expect} from '@jest/globals';
// remember to export `toBeWithinRange` as well
import {toBeWithinRange} from './toBeWithinRange';
expect.extend({
toBeWithinRange,
});
异步匹配器
expect.extend
还支持异步匹配器。 异步匹配器返回 Promise,因此你需要等待返回值。 让我们使用一个示例匹配器来说明它们的用法。 我们将实现一个名为 toBeDivisibleByExternalValue
的匹配器,其中可整除的数字将从外部源中提取。
expect.extend({
async toBeDivisibleByExternalValue(received) {
const externalValue = await getExternalValueFromRemoteSource();
const pass = received % externalValue == 0;
if (pass) {
return {
message: () =>
`expected ${received} not to be divisible by ${externalValue}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${received} to be divisible by ${externalValue}`,
pass: false,
};
}
},
});
test('is divisible by external value', async () => {
await expect(100).toBeDivisibleByExternalValue();
await expect(101).not.toBeDivisibleByExternalValue();
});
自定义匹配器 API
匹配器应该返回一个带有两个键的对象(或对象的 Promise)。 pass
表示是否匹配,message
提供一个不带参数的函数,在失败时返回错误消息。 因此,当 pass
为 false 时,message
应该返回 expect(x).yourMatcher()
失败时的错误消息。 当 pass
为 true 时,message
应该返回 expect(x).not.yourMatcher()
失败时的错误消息。
调用匹配器时,将参数传递给 expect(x)
,然后将参数传递给 .yourMatcher(y, z)
:
expect.extend({
yourMatcher(x, y, z) {
return {
pass: true,
message: () => '',
};
},
});
这些辅助函数和属性可以在 this
上的自定义匹配器中找到:
this.isNot
一个布尔值,让你知道此匹配器是使用否定的 .not
修饰符调用的,允许你显示清晰且正确的匹配器提示(请参阅示例代码)。
this.promise
一个字符串,允许你显示清晰且正确的匹配器提示:
'rejects'
如果使用 Promise.rejects
修饰符调用匹配器'resolves'
如果使用 Promise.resolves
修饰符调用匹配器''
如果没有使用 Promise 修饰符调用匹配器
this.equals(a, b, customTesters?)
这是一个深度相等函数,如果两个对象具有相同的值(递归地),它将返回 true
。 它可以选择使用自定义相等性测试程序列表来应用深度相等性检查(请参阅下面的 this.customTesters
)。
this.expand
一个布尔值,让你知道这个匹配器是使用 expand
选项调用的。 当使用 --expand
标志调用 Jest 时,this.expand
可用于确定 Jest 是否应显示完整的差异和错误。
this.utils
this.utils
上公开了许多有用的工具,主要包括 jest-matcher-utils
的导出。
最有用的是 matcherHint
、printExpected
和 printReceived
,可以很好地格式化错误消息。 例如,看一下 toBe
匹配器的实现:
const {diff} = require('jest-diff');
expect.extend({
toBe(received, expected) {
const options = {
comment: 'Object.is equality',
isNot: this.isNot,
promise: this.promise,
};
const pass = Object.is(received, expected);
const message = pass
? () =>
// eslint-disable-next-line prefer-template
this.utils.matcherHint('toBe', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(received)}`
: () => {
const diffString = diff(expected, received, {
expand: this.expand,
});
return (
// eslint-disable-next-line prefer-template
this.utils.matcherHint('toBe', undefined, undefined, options) +
'\n\n' +
(diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(received)}`)
);
};
return {actual: received, message, pass};
},
});
这将打印如下内容:
expect(received).toBe(expected)
Expected value to be (using Object.is):
"banana"
Received:
"apple"
当断言失败时,错误消息应向用户提供尽可能多的必要信号,以便他们能够快速解决问题。 你应该制作精确的失败消息,以确保自定义断言的用户拥有良好的开发者体验。
this.customTesters
如果你的匹配器使用 this.equals
进行深度相等检查,你可能希望将用户提供的自定义测试器传递给 this.equals
。 用户使用 addEqualityTesters
API 提供的自定义相等测试器可在此属性上使用。 内置的 Jest 匹配器将 this.customTesters
(以及其他内置测试器)传递给 this.equals
以进行深度相等,并且你的自定义匹配器可能想要执行相同的操作。
自定义快照匹配器
要在自定义匹配器中使用快照测试,你可以导入 jest-snapshot
并在匹配器中使用它。
这是一个快照匹配器,它修剪要存储的给定长度 .toMatchTrimmedSnapshot(length)
的字符串:
const {toMatchSnapshot} = require('jest-snapshot');
expect.extend({
toMatchTrimmedSnapshot(received, length) {
return toMatchSnapshot.call(
this,
received.substring(0, length),
'toMatchTrimmedSnapshot',
);
},
});
it('stores only 10 characters', () => {
expect('extra long string oh my gerd').toMatchTrimmedSnapshot(10);
});
/*
Stored snapshot will look like:
exports[`stores only 10 characters: toMatchTrimmedSnapshot 1`] = `"extra long"`;
*/
还可以为内联快照创建自定义匹配器,快照将正确添加到自定义匹配器中。 但是,当第一个参数是属性匹配器时,内联快照将始终尝试附加到第一个参数或第二个参数,因此不可能在自定义匹配器中接受自定义参数。
const {toMatchInlineSnapshot} = require('jest-snapshot');
expect.extend({
toMatchTrimmedInlineSnapshot(received, ...rest) {
return toMatchInlineSnapshot.call(this, received.substring(0, 10), ...rest);
},
});
it('stores only 10 characters', () => {
expect('extra long string oh my gerd').toMatchTrimmedInlineSnapshot();
/*
The snapshot will be added inline like
expect('extra long string oh my gerd').toMatchTrimmedInlineSnapshot(
`"extra long"`
);
*/
});
async
如果你的自定义内联快照匹配器是异步的,即使用 async
-await
,你可能会遇到类似 "不支持同一调用的多个内联快照" 的错误。 Jest 需要额外的上下文信息来查找自定义内联快照匹配器用于正确更新快照的位置。
const {toMatchInlineSnapshot} = require('jest-snapshot');
expect.extend({
async toMatchObservationInlineSnapshot(fn, ...rest) {
// The error (and its stacktrace) must be created before any `await`
this.error = new Error();
// The implementation of `observe` doesn't matter.
// It only matters that the custom snapshot matcher is async.
const observation = await observe(async () => {
await fn();
});
return toMatchInlineSnapshot.call(this, recording, ...rest);
},
});
it('observes something', async () => {
await expect(async () => {
return 'async action';
}).toMatchTrimmedInlineSnapshot();
/*
The snapshot will be added inline like
await expect(async () => {
return 'async action';
}).toMatchTrimmedInlineSnapshot(`"async action"`);
*/
});
帮助
通常,jest
会尝试匹配测试中预期的每个快照。
有时,如果先前的快照失败,则继续测试可能没有意义。 例如,当你在各种转换后制作状态机的快照时,一旦一个转换产生错误的状态,你就可以中止测试。
在这种情况下,你可以实现一个自定义快照匹配器,该快照匹配器会抛出第一个不匹配的情况,而不是收集每个不匹配的情况。
const {toMatchInlineSnapshot} = require('jest-snapshot');
expect.extend({
toMatchStateInlineSnapshot(...args) {
this.dontThrow = () => {};
return toMatchInlineSnapshot.call(this, ...args);
},
});
let state = 'initial';
function transition() {
// Typo in the implementation should cause the test to fail
if (state === 'INITIAL') {
state = 'pending';
} else if (state === 'pending') {
state = 'done';
}
}
it('transitions as expected', () => {
expect(state).toMatchStateInlineSnapshot(`"initial"`);
transition();
// Already produces a mismatch. No point in continuing the test.
expect(state).toMatchStateInlineSnapshot(`"loading"`);
transition();
expect(state).toMatchStateInlineSnapshot(`"done"`);
});