跳至主内容
版本:下一篇

Expect

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

编写测试时,经常需要验证值是否符合特定条件。expect 提供了一系列"匹配器",可帮助您验证不同类型的内容。

技巧

更多由 Jest 社区维护的匹配器,请查看 jest-extended

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

信息

本页中的 TypeScript 示例仅在显式导入 Jest API 的情况下才能按文档所述正常工作:

import {expect, jest, test} from '@jest/globals';

有关如何设置 Jest 与 TypeScript 配合使用的详细信息,请查阅入门指南

参数详解


Expect 断言

expect(value)

每次需要测试某个值时都会用到 expect 函数。通常不会单独调用 expect,而是将 expect 与"匹配器"函数配合使用来断言值的特性。

通过示例更容易理解。假设您有个应返回字符串 'grapefruit' 的方法 bestLaCroixFlavor(),测试代码如下:

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 等待。

或结合 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,测试本质仍是异步的。因此需要通过返回解包后的断言告知 Jest 等待。

或结合 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 匹配器检查的是引用一致性,但当断言失败时,它会报告值的深度差异对比。如果属性差异无法帮助理解测试失败原因(特别是当报告内容冗长时),可将比较逻辑移至 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()

使用 .toHaveBeenCalled 确保模拟函数已被调用。

例如,假设存在 drinkAll(drink, flavour) 函数,该函数接收 drink 方法并将其应用于所有可用饮料。可通过以下测试用例验证 drink 是否被调用:

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)

使用 .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, ...)

使用 .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, ...)

对模拟函数使用 .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, ....)

对模拟函数使用 .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');
});
备注

nth 参数必须是大于等于 1 的正整数。

.toHaveReturned()

对模拟函数使用 .toHaveReturned 可检测该函数是否成功返回(即未抛出错误)至少一次。例如,设模拟函数 drink 返回 true,可编写:

test('drinks returns', () => {
const drink = jest.fn(() => true);

drink();

expect(drink).toHaveReturned();
});

.toHaveReturnedTimes(number)

使用 .toHaveReturnedTimes 确保模拟函数成功返回(即未抛出错误)精确次数。任何抛出错误的调用不计入成功返回次数。

例如,设模拟函数 drink 返回 true,可编写:

test('drink returns twice', () => {
const drink = jest.fn(() => true);

drink();
drink();

expect(drink).toHaveReturnedTimes(2);
});

.toHaveReturnedWith(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)

使用 .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)

使用 .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)');
});
备注

nth 参数必须是大于等于 1 的正整数。

.toHaveLength(number)

使用 .toHaveLength 检查对象是否具有 .length 属性且其值等于特定数字。

这在检查数组或字符串长度时特别有用。

expect([1, 2, 3]).toHaveLength(3);
expect('abc').toHaveLength(3);
expect('').not.toHaveLength(5);

.toHaveProperty(keyPath, value?)

使用 .toHaveProperty 检查对象在指定路径 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)。

直观的相等比较常会失败,因为十进制(base 10)值的算术运算在有限精度的二进制(base 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 具体返回什么值——它可能返回 falsenull0,而你的代码仍能正常工作。因此,如果你想测试饮用 La Croix 后没有错误发生,可以这样写:

test('drinking La Croix does not lead to errors', () => {
drinkSomeLaCroix();
expect(getErrors()).toBeFalsy();
});

在 JavaScript 中,有六个假值:false0''nullundefinedNaN。其他所有值都是真值。

.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 中,有六个假值:false0''nullundefinedNaN。其他所有值都是真值。

.toBeUndefined()

使用 .toBeUndefined 检查变量是否为 undefined。例如,若需验证函数 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');
});

此匹配器同样支持其他可迭代对象,如字符串、Set、NodeList 和 HTMLCollection。

.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 不会对两个错误执行深度相等检查。只有错误的 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 总是在快照名称的末尾附加一个数字,但在 单个 ittest 块中,简短的描述性提示可能比数字更能区分 多个 快照。Jest 在相应的 .snap 文件中按名称对快照进行排序。

.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)

确保值匹配最近的快照。

您可以提供一个可选的 propertyMatchers 对象参数,其中包含预期属性子集对应的非对称匹配器值,前提是接收值必须是对象实例。这类似于对部分属性使用 toMatchObject 进行灵活匹配后,再对其余属性执行精确匹配的快照测试。

首次运行测试时,Jest 会将 inlineSnapshot 字符串参数添加到测试文件的匹配器中(而非外部 .snap 文件)。

请查看内联快照部分以获取更多信息。

.toStrictEqual(value)

使用 .toStrictEqual 验证对象是否具有相同的结构和类型。

.toEqual 的区别:

  • 检查包含 undefined 属性的键,例如 {a: undefined, b: 2} 不等于 {b: 2}

  • 考虑 undefined 元素,例如 [2] 不等于 [2, undefined]

  • 检查数组稀疏性,例如 [, 1] 不等于 [undefined, 1]

  • 检查对象类型,例如包含字段 ab 的类实例不等于具有字段 ab 的字面量对象。

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?)

使用 .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 总是在快照名称的末尾附加一个数字,但在 单个 ittest 块中,简短的描述性提示可能比数字更能区分 多个 快照。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() 匹配除 nullundefined 外的任何值。你可以在 toEqualtoHaveBeenCalledWith 中使用它代替字面值。例如,要检查模拟函数是否被传入非空参数:

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) 匹配由给定构造函数创建的值,或符合传递类型的原始值。可在 toEqualtoHaveBeenCalledWith 中代替字面值使用。例如,要检查模拟函数是否被传入数字:

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) 匹配包含预期数组中所有元素的接收数组。即预期数组是接收数组的子集,因此允许接收数组包含预期数组之外的元素。

可在以下场景替代字面值使用:

  • toEqualtoHaveBeenCalledWith

  • 匹配 objectContainingtoMatchObject 中的属性

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.arrayOf(value)

expect.arrayOf(value) 匹配所有元素均符合预期值的接收数组,用于断言数组中每个元素满足特定条件或类型。

示例:

test('all elements in array are strings', () => {
expect(['apple', 'banana', 'cherry']).toEqual(
expect.arrayOf(expect.any(String)),
);
});

该匹配器特别适用于验证包含复杂结构的数组:

test('array of objects with specific properties', () => {
expect([
{id: 1, name: 'Alice'},
{id: 2, name: 'Bob'},
]).toEqual(
expect.arrayOf(
expect.objectContaining({
id: expect.any(Number),
name: expect.any(String),
}),
),
);
});

expect.not.arrayOf(value)

expect.not.arrayOf(value) 匹配并非所有元素都符合预期匹配器的接收数组。

示例:

test('not all elements in array are strings', () => {
expect(['apple', 123, 'cherry']).toEqual(
expect.not.arrayOf(expect.any(String)),
);
});

expect.closeTo(number, numDigits?)

expect.closeTo(number, numDigits?) 适用于比较对象属性或数组项中的浮点数。若需比较单个数字,请改用 .toBeCloseTo

可选参数 numDigits 限定小数点的检查位数。默认值 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) 匹配递归符合预期属性的接收对象。即预期对象是接收对象的子集,因此接收对象必须包含预期对象中的属性。

预期对象中的属性值可用匹配器(如 expect.anything())替代字面值。

例如,假设需验证 onPress 函数被调用时传入的 Event 对象包含 event.xevent.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) 会匹配成功。

可在以下场景替代字面值使用:

  • toEqualtoHaveBeenCalledWith

  • 匹配 arrayContaining 中的元素

  • 匹配 objectContainingtoMatchObject 中的属性

此示例展示了如何在 expect.arrayContaining 内部嵌套使用 expect.stringMatching 实现多重非对称匹配。

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 接收两个回调函数 callback1callback2,它会以未知顺序异步调用这两个回调。测试代码如下:

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 类实例时使用此自定义相等方法。你可以添加自定义的相等测试器,让 toEqual 在比较 Volume 类时能够检测并应用自定义逻辑:

Volume.js
// 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;
}
}
}
areVolumesEqual.js
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]);
__tests__/Volume.test.js
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

自定义测试器是返回比较结果的函数(truefalse),若测试器无法处理当前对象需委托给其他测试器(如内置相等性测试器)时返回 undefined

自定义测试器接收 3 个参数:待比较的两个对象,以及自定义测试器数组(用于递归测试,详见下文)。

在自定义测试器内部可通过 this 访问以下辅助函数和属性:

this.equals(a, b, customTesters?)

这是一个深度相等函数,当两个对象具有相同的值(递归比较)时将返回 true。它可选地接受一组自定义相等测试器应用于深度相等检查。如果使用此函数,请传入测试器接收到的自定义测试器,这样 equals 执行的深层相等检查也能利用测试作者可能配置的自定义测试器。更多细节请参阅递归自定义相等测试器部分的示例。

匹配器 vs 测试器

匹配器是 expect 上的可用方法,例如 expect().toEqual()toEqual 就是一个匹配器。测试器则是匹配器用于执行相等检查以确定对象是否相同的方法。

当您希望为测试作者提供可在测试中使用的自定义断言时,自定义匹配器非常有用。例如 expect.extend 部分的 toBeWithinRange 示例就是自定义匹配器的典型用例。有时测试作者可能需要断言两个数字完全相等,此时应使用 toBe;而有时测试作者可能希望测试具有一定灵活性,这时 toBeWithinRange 会是更合适的断言方式。

自定义相等测试器适用于全局扩展 Jest 匹配器,为所有相等比较应用自定义逻辑。测试作者无法针对特定断言开启自定义测试器而关闭其他断言的测试器(如果需要这种灵活性则应使用自定义匹配器)。例如,定义如何检查两个 Volume 对象在所有匹配器中的相等性,就是自定义相等测试器的典型应用场景。

递归自定义相等测试器

如果您的自定义相等测试器需要测试具有需深度比较属性的对象,应使用测试器可访问的 this.equals 辅助方法。此 equals 方法与 Jest 内部所有深度相等比较使用的深度相等方法是同一个。它会调用您的自定义相等测试器,并接受自定义相等测试器数组作为第三个参数。自定义相等测试器同样会接收自定义测试器数组作为第三个参数。请将此参数传入 equals 的第三个参数,这样对象深层结构中的后续相等检查也能利用自定义相等测试器。

例如,假设您有包含 Author 类数组的 Book 类,且这两个类都有自定义测试器。Book 的自定义测试器需要对 Author 数组进行深度相等检查,并传入接收到的自定义测试器,从而应用 Author 的自定义相等测试器:

customEqualityTesters.js
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 匹配器:

toBeWithinRange.js
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,
});
__tests__/ranges.test.js
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),
});
});
toBeWithinRange.d.ts
// 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 {};
技巧

匹配器的类型声明可以放在 .d.ts 文件中,也可以放在导入的 .ts 模块中(分别参见上面的 JS 和 TS 示例)。如果声明保存在 .d.ts 文件中,请确保它包含在程序中且是有效的模块,即至少包含一个空的 export {}

技巧

无需将 toBeWithinRange 模块导入测试文件,你可以通过将 expect.extend 调用移至 setupFilesAfterEnv 脚本来为所有测试启用该匹配器:

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 判断是否需要显示完整差异和错误信息。

this.utils

this.utils 包含多个实用工具,主要来自 jest-matcher-utils 的导出。

最实用的工具包括 matcherHintprintExpectedprintReceived,它们能优雅地格式化错误信息。例如,参考 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"`);
});

可序列化属性

SERIALIZABLE_PROPERTIES

可序列化属性是 Jest 认为可序列化的属性集合。该集合用于确定某个属性是否应被序列化。如果对象包含不在此集合中的属性,则视为不可序列化,且不会在错误信息中打印。

你可以添加自定义属性到此集合中,以确保对象正确打印。例如,如果你有一个 Volume 类,且希望仅打印 amountunit 属性,可将其加入 SERIALIZABLE_PROPERTIES

import {SERIALIZABLE_PROPERTIES} from 'jest-matcher-utils';

class Volume {
constructor(amount, unit) {
this.amount = amount;
this.unit = unit;
}

get label() {
throw new Error('Not implemented');
}
}

Volume.prototype[SERIALIZABLE_PROPERTIES] = ['amount', 'unit'];

expect(new Volume(1, 'L')).toEqual(new Volume(10, 'L'));

这将确保错误信息中仅打印 amountunit 属性,忽略 label 属性。

expect(received).toEqual(expected) // deep equality

Expected: {"amount": 10, "unit": "L"}
Received: {"amount": 1, "unit": "L"}