Logo

Jest 自定义匹配器

了解匹配器是什么,为什么我们需要它们,以及如何开发自己的匹配器。

概念

Jest 自定义匹配器

了解匹配器是什么,为什么我们需要它们,以及如何开发自己的匹配器。

什么是匹配器?

匹配器(或断言)是一种用于检查特定条件的函数。通常,它用于比较两个值。以下是 Jest 中提供的一些基本匹配器:

1expect(1).toBe(1); // 检查值和引用相等性 2expect({a: 'b'}).toEqual({a: 'b'}); // 检查深层值相等性 3expect('abacaba').toMatch(/bac/); // 检查字符串是否匹配正则表达式 4expect({a: 'b', b: 'c'}).toMatchObject({a: 'b'}); // 检查对象的部分匹配 5expect([1, 2, 3]).toContainEqual(2); // 检查数组是否包含某个元素 6expect(2).not.toEqual(3); // 使用 not 来否定任何匹配器 7expect({a: 'b'}).toMatchObject({ 8 a: expect.any(String) 9}); // 类型检查 10 11const mockFunction = jest.fn(); // 创建一个模拟函数 12expect(mockFunction).toHaveBeenCalled(); // 检查是否被调用 13expect(mockFunction).toHaveBeenCalledWith('abacaba'); // 检查调用时的参数

为什么需要自定义匹配器?

尽管 Jest 本身功能非常强大,但项目中总会有一些特定的需求可以进一步优化。通过为常见场景创建自定义 Jest 匹配器,可以提升代码可读性并减少重复代码。

在我的一个项目中,我需要测试某个值是否为格式正确的 ISO 日期。虽然可以用正则表达式实现,但由于这个检查频繁出现,将其封装成自定义匹配器非常值得。

添加设置文件

在添加匹配器之前,首先需要配置 Jest 的设置文件。设置文件用于初始化环境,例如添加自定义匹配器、启用模拟、配置 Jest 等。在项目根目录下创建一个名为 setupJest.js 的文件作为设置文件。然后在 Jest 配置中添加以下内容(位于 package.json 或 jest.config.js):

1"setupFilesAfterEnv": ["./setupJest.js"]

注意:对于使用 CRA(包括我们的课程项目)创建的项目,该文件已存在(位于 src/setupTests.js),但上述步骤无需执行。如果你希望为 Selenium 版本的 Jest 编写自定义匹配器,则需新建 setupJest.js 文件,并将上述配置添加到我们专门为 Selenium 创建的 jest.config.js 中。

编写自定义 Jest 匹配器

在这个示例中,我将编写一个匹配器来判断一个字符串是否为格式正确的 ISO 日期。这可以通过 expect.extend 函数实现。它接收一个对象,键为匹配器名称,值为执行实际匹配的函数。以下是 setupJest.js 中的内容:

1expect.extend({ 2 toBeISODate(received) { 3 // 此正则表达式用于检查格式 4 if ( 5 !/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(received) 6 ) { 7 return { 8 pass: false, 9 message: `Expected ${received} to be a valid ISO date string`, 10 }; 11 } 12 13 // 检查 JavaScript 是否能正确解析该字符串 14 const d = new Date(received); 15 return d.toISOString() === received 16 ? { 17 pass: true, 18 message: `Expected ${received} not to be a valid ISO date string`, 19 } 20 : { 21 pass: false, 22 message: `Expected ${received} to be a valid ISO date string`, 23 }; 24 }, 25});

可以看到,toBeISODate 是一个接受单个参数 received 的函数。你也可以接受更多参数,这些参数会直接传递给匹配器(稍后会详细介绍)。第 4 到第 6 行检查 received 是否符合正则格式;第 14 和第 15 行检查 JavaScript 是否能正确解析该字符串。

匹配器必须返回一个包含两个属性的对象:pass 和 message。pass 很简单:如果检查通过则为 true,否则为 false。message 稍微复杂一些:当 pass=false 时,它是 expect(something).toBeISODate() 失败时显示的错误信息;当 pass=true 时,它是 expect(something).not.toBeISODate() 失败时显示的错误信息。测试通过时,该消息不会显示。现在你可以这样使用这个匹配器:

1const timestamp = get_timestamp_from_somewhere_unreliable(); 2expect(timestamp).toBeISODate();

接受更多参数

toBeISODate() 匹配器不接受额外参数,也无需如此。但假设我们要编写一个匹配器,用于检查某个数字是否为另一个数字的幂。以下是名为 toBePowerOf() 的匹配器代码:

1expect.extend({ 2 toBePowerOf(received, power) { 3 if (typeof power !== 'number') { 4 throw new Error('expected power to be a number'); 5 } 6 7 if (typeof received !== 'number') { 8 throw new Error('expected value to be a number'); 9 } 10 11 let receivedCopy = received; 12 while (receivedCopy % power === 0) receivedCopy = receivedCopy / power; 13 14 return receivedCopy === 1 ? ({ 15 pass: true, 16 message: `Expected ${received} not to be a power of ${power}` 17 }) : ({ 18 pass: false, 19 message: `Expected ${received} to be a power of ${power}` 20 }); 21 } 22});

该匹配器多接受一个 power 参数。你可以在代码中这样使用:

1expect(8).toBePowerOf(2); // 通过 2expect(10).toBePowerOf(3); // 失败 3expect('abacaba').toBePowerOf(); // 抛出错误

关于集成测试的更多内容