Jest - Mocking
在本章中,我们将学习Jest 中的 mocking。Mocking 允许您用模拟版本替换实际函数、模块或 API 调用,以便您可以独立测试代码的各个部分。
使用 mock 可让您的测试更快、更可靠,并让您专注于核心逻辑,同时控制外部依赖项。让我们开始吧。
Jest 中的 Mocking 是什么?
Jest 中的 Mocking 意味着用模拟版本(与真实版本一样的假版本)替换实际函数、模块或组件,以测试您的代码如何与它们交互。这可以帮助您专注于测试代码的特定部分,同时避免依赖外部系统,从而使您的测试更快、更可靠。
在以下情况下,模拟尤其有用:
- 外部服务(如 API 或数据库)速度慢、不可用或不需要进行测试。
- 您希望专注于测试代码的特定部分,而不受其他部分的干扰。
- 您需要模拟可能难以用真实数据重现的错误或异常情况(边缘情况)。
为什么模拟在 Jest 中很重要?
模拟很重要,因为它:
- 隔离代码:您可以通过用模拟替换实际依赖项来独立测试代码的特定部分。
- 改进测试:模拟避免调用实际函数或外部系统,从而使测试运行更快。
- 提供控制:Mocking 允许您定义依赖项的行为方式,例如返回特定值或导致错误。
- 简化测试边缘情况:Mocking 可以更轻松地测试罕见情况,例如来自外部服务的错误或意外数据。
Jest 中的 Mocking 类型
在 Jest 中,您可以根据需要测试的内容使用不同类型的 Mocking。以下是可用的主要模拟类型:
Jest - 函数模拟
Jest 中的函数模拟意味着创建函数的虚假版本以用于测试。这些模拟函数的行为与真实函数类似,但允许您控制其操作、跟踪调用、决定返回的内容,以及在不运行实际代码的情况下测试不同情况。
创建模拟函数
您可以使用 jest.fn() 创建模拟函数。它的行为与真实函数类似,但允许您控制其行为,例如返回值。
// 模拟函数返回 6 const mockFunction = jest.fn().mockReturnValue(6);
跟踪调用
您可以跟踪模拟函数被调用的次数以及它接收到的参数。
const mockFunction = jest.fn(); mockFunction(1, 2); // 验证 mock 是否使用 1 和 2 调用 expect(mockFunction).toHaveBeenCalledWith(1, 2);
模拟多个返回值
您可以使用 mockReturnValueOnce() 使模拟函数为不同的调用返回不同的值。
const mockFunction = jest.fn() // 第一次调用返回 1 .mockReturnValueOnce(1) // 第二次调用返回 2 .mockReturnValueOnce(2);
自定义实现
您可以使用 mockImplementation() 为模拟函数定义自定义行为。
// 将两个数字相加 const mockFunction = jest.fn().mockImplementation((a, b) => a + b);
重置模拟
每次测试后,您可以使用 mockReset() 重置模拟的状态。这可确保一个测试不会影响下一个测试。
const mockFunction = jest.fn(); // 重置模拟的状态 mockFunction.mockReset();
Jest - 模块模拟
有时,您可能希望模拟整个模块,而不仅仅是一个函数。当您想要测试代码的特定部分而不依赖于实际的模块实现(例如 API 或数据库)时,这很有用。
如何模拟模块?
要模拟模块,您可以使用 jest.mock()。这会用模拟版本替换实际的模块实现。
示例
// 假设我们有一个包含 add 函数的 math.js 模块 jest.mock('./math'); // 模拟数学模块 import * as math from './math'; test('mock module example', () => { // 模拟 add 函数以返回 10 math.add.mockReturnValue(10); // 模拟返回 10,而不是实际实现 expect(math.add(2, 3)).toBe(10); });
Jest - 手动模拟
手动模拟让您能够更好地控制 Jest 的模拟。您可以为模块创建自己的模拟,将其放在 __mocks__ 目录中,Jest 将在测试期间使用您的模拟而不是真实模块。
如何创建手动模拟?
在 Jest 中,您可以按照以下简单步骤创建手动模拟:
- 创建 __mocks__ 文件夹:将其放在要模拟的模块旁边。
- 在 __mocks__ 文件夹中定义模拟行为。
示例
- 原始模块 (math.js):这是您要模拟的真实实现。
// math.js export const add = (a, b) => a + b;
// __mocks__/math.js export const add = jest.fn(() => 42); // 始终返回 42
jest.mock('./math'); // 模拟模块 expect(add()).toBe(42); // 测试模拟行为
Jest 模拟计时器
Jest 中的计时器模拟允许您在测试期间控制 setTimeout、setInterval 和 clearTimeout 等函数。当您想要测试与时间相关的行为而无需等待实际的时间延迟时,这很有用。
如何模拟计时器?
要模拟计时器,请使用 jest.useFakeTimers() 将真实计时器替换为假计时器,并使用 jest.runAllTimers() 等方法控制时间的流逝。
示例
test('mocking setTimeout with fake timers', () => { // 启用假计时器 jest.useFakeTimers(); const mockCallback = jest.fn(); // 使用模拟回调设置超时 setTimeout(mockCallback, 1000); // 将时间向前移动并触发所有计时器 jest.runAllTimers(); // 验证模拟回调被调用 expect(mockCallback).toHaveBeenCalled(); });
Jest - 模拟类
在 Jest 中,您可以模拟类方法或整个类,这在测试依赖于类的代码时很有用。模拟允许您复制类行为,而无需运行实际代码或引起任何副作用。
如何模拟类?
要模拟类,您可以对单个方法使用 jest.fn() 或模拟整个类。
示例
// 定义 Car 类 class Car { start() { return 'Car started'; } } // 模拟 Car 类的 start 方法 test('mocking class methods', () => { // 模拟 start 方法 const mockStart = jest.fn().mockReturnValue('Mocked start'); const car = new Car(); // 用模拟替换 start 方法 car.start = mockStart; // 验证模拟行为 expect(car.start()).toBe('Mocked start'); });
Jest - 模拟 API 调用
模拟 API 调用在 Jest 中意味着为 API 请求创建虚假响应,而不是实际连接到真实 API。这有助于让您的测试更快、更可靠,因为您不需要依赖真实的外部服务或互联网来运行它们。
如何模拟 API 调用?
您可以模拟 axios 或 fetch 等库,假装服务器正在响应,而无需实际向真实服务器发出请求。
示例
// 这会模拟 axios 库以防止实际的 API 调用 jest.mock('axios'); import axios from 'axios'; test('mocking API call', async () => { // 模拟 axios.get() 的响应 axios.get.mockResolvedValue({ data: { user: 'John' } }); // 调用 API 函数(与实际代码相同) const response = await axios.get('/user'); // 检查是否返回了模拟响应 expect(response.data.user).toBe('John'); });
Jest - Spy Mocks
Spy mocks 跟踪现有函数或方法的调用方式,而无需更改其行为。您可以使用 jest.spyOn() 来观察对象上的方法,然后检查它在测试中的使用方式。
如何使用 Spy Mocks?
您可以使用 jest.spyOn() 来观察函数或方法的调用方式。
示例
// 使用 startEngine 方法创建 car 对象 const car = { startEngine: () => { return 'Engine started'; }, }; test('监视 startEngine 方法', () => { // 监视 startEngine 方法 const spy = jest.spyOn(car, 'startEngine'); car.startEngine(); // 调用该方法 // 检查是否调用了 startEngine expect(spy).toHaveBeenCalled(); spy.mockRestore(); // 恢复原始方法 });