Expect
在编写测试时,您经常需要检查值是否满足某些条件。expect
使您可以访问许多“匹配器”,这些匹配器可以让您验证不同的内容。
有关 Jest 社区维护的其他 Jest 匹配器,请查看 jest-extended
.
本页面的 TypeScript 示例只有在您显式导入 Jest API 时才能按文档工作
import {expect, jest, test} from '@jest/globals';
有关如何使用 TypeScript 设置 Jest 的详细信息,请参阅 入门 指南。
参考
- Expect
- 修饰符
- 匹配器
.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
expect(value)
每次要测试一个值时,都会使用expect
函数。您很少会单独调用expect
。相反,您将使用expect
以及一个“匹配器”函数来断言有关值的内容。
通过一个例子更容易理解这一点。假设您有一个方法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,因此测试仍然是异步的。因此,您需要通过返回解开的断言来告诉 Jest 等待。
或者,您可以在.resolves
中结合使用async/await
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,因此测试仍然是异步的。因此,您需要通过返回解开的断言来告诉 Jest 等待。
或者,您可以在.rejects
中结合使用async/await
。
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
匹配器检查引用标识,但如果断言失败,它会报告值的深度比较。如果属性之间的差异不能帮助您理解测试失败的原因,尤其是如果报告很大,那么您可能会将比较移入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()
使用.toHaveBeenCalled
确保模拟函数已被调用。
例如,假设您有一个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
来确保模拟函数成功返回(即没有抛出错误)的次数。任何调用模拟函数并抛出错误的调用都不会计入函数返回的次数。
例如,假设你有一个模拟函数 drink
,它返回 true
。你可以写
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
参数限制了检查小数点后的位数。对于默认值 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()
当你不在乎值是什么,并且想要确保值在布尔上下文中为假时,使用 .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)
相同,但错误消息更友好。因此,当你想要检查某事物是否为 null 时,使用 .toBeNull()
。
function bloop() {
return null;
}
test('bloop returns null', () => {
expect(bloop()).toBeNull();
});
.toBeTruthy()
当你不在乎值是什么,并且想要确保值在布尔上下文中为真时,使用 .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
来递归地比较对象实例的所有属性(也称为“深度”相等)。它调用 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
不会对两个错误执行深度相等检查。仅考虑 Error 的 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 对象是否与对象的属性子集匹配。它将匹配接收到的对象,这些对象的属性不在预期对象中。
你还可以传递一个对象数组,在这种情况下,该方法仅在接收到的数组中的每个对象都与预期数组中的对应对象匹配(在上面描述的 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
对象参数,该参数包含作为预期属性子集的值的非对称匹配器,**如果**接收到的值将是**对象**实例。它类似于 toMatchObject
,但对属性子集使用灵活的标准,然后对其余属性使用快照测试作为精确标准。
您可以提供一个可选的 hint
字符串参数,该参数将附加到测试名称。虽然 Jest 始终在快照名称末尾附加一个数字,但简短的描述性提示可能比数字更有用,可以区分**单个** it
或 test
块中的**多个**快照。Jest 会在相应的 .snap
文件中按名称对快照进行排序。
.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)
确保值与最新的快照匹配。
您可以提供一个可选的 propertyMatchers
对象参数,该参数包含作为预期属性子集的值的非对称匹配器,**如果**接收到的值将是**对象**实例。它类似于 toMatchObject
,但对属性子集使用灵活的标准,然后对其余属性使用快照测试作为精确标准。
Jest 会在测试文件(而不是外部 .snap
文件)中将 inlineSnapshot
字符串参数添加到匹配器中,这是测试第一次运行时。
查看有关 内联快照 的部分以获取更多信息。
.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();
});
您必须将代码包装在函数中,否则错误将不会被捕获,断言将失败。
您可以提供一个可选参数来测试是否抛出了特定错误
- 正则表达式:错误消息**匹配**模式
- 字符串:错误消息**包含**子字符串
- 错误对象:错误消息**等于**对象的 message 属性
- 错误类:错误对象是**类的实例**
例如,假设 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 始终在快照名称末尾附加一个数字,但简短的描述性提示可能比数字更有用,可以区分**单个** it
或 test
块中的**多个**快照。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 会在测试文件(而不是外部 .snap
文件)中将 inlineSnapshot
字符串参数添加到匹配器中,这是测试第一次运行时。
查看有关 内联快照 的部分以获取更多信息。
非对称匹配器
expect.anything()
expect.anything()
匹配除 null
或 undefined
之外的任何内容。您可以在 toEqual
或 toHaveBeenCalledWith
中使用它,而不是使用文字值。例如,如果您想检查模拟函数是否被非空参数调用
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
或 toHaveBeenCalledWith
中使用它,而不是使用文字值。例如,如果您想检查模拟函数是否被数字调用
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)
匹配包含预期数组中所有元素的接收数组。也就是说,预期数组是接收数组的**子集**。因此,它匹配包含**不在**预期数组中的元素的接收数组。
您可以使用它来代替文字值
- 在
toEqual
或toHaveBeenCalledWith
中 - 在
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
参数限制了**小数点后**要检查的位数。对于默认值 2
,测试标准是 Math.abs(expected - received) < 0.005 (即 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)
匹配任何递归匹配预期属性的接收对象。也就是说,预期对象是接收对象的**子集**。因此,它匹配包含**存在于**预期对象中的属性的接收对象。
在预期对象中,您可以使用匹配器、expect.anything()
等,而不是使用文字属性值。
例如,假设我们期望 onPress
函数被 Event
对象调用,并且我们只需要验证事件是否具有 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)
匹配任何不递归匹配预期属性的接收对象。也就是说,预期对象**不是接收对象的子集**。因此,它匹配包含**不在**预期对象中的属性的接收对象。
它是 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.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
或toHaveBeenCalledWith
中 - 在
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.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
回调完成。我们可以使用以下方法进行测试
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
(以及其他相等匹配器)在比较体积类时使用此自定义相等方法。您可以添加一个自定义相等测试器,让 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
的第三个参数中,以便对象中更深层的任何进一步的相等检查也可以利用自定义相等测试器。
例如,假设您有一个包含 Author
类数组的 Book
类,这两个类都有自定义测试器。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]);
请记住,将您的相等测试器定义为普通函数,而不是箭头函数,以便访问测试器上下文辅助函数(例如 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 TypeError('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 TypeError('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
选项调用。当 Jest 使用 --expand
标志调用时,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.slice(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.slice(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
-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"`);
});