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 中手动模拟: 使用自定义行为创建模块的模拟版本。
  • // __mocks__/math.js
    export const add = jest.fn(() => 42); // 始终返回 42
    
  • 在测试中使用模拟: 每当在测试中导入模块时,Jest 都会自动使用模拟。
  • jest.mock('./math'); // 模拟模块
    expect(add()).toBe(42); // 测试模拟行为
    

Jest 模拟计时器

Jest 中的计时器模拟允许您在测试期间控制 setTimeoutsetIntervalclearTimeout 等函数。当您想要测试与时间相关的行为而无需等待实际的时间延迟时,这很有用。

如何模拟计时器?

要模拟计时器,请使用 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 调用?

您可以模拟 axiosfetch 等库,假装服务器正在响应,而无需实际向真实服务器发出请求。

示例

// 这会模拟 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(); // 恢复原始方法
});