Jest - 性能和高级主题

在本章中,我们将介绍高级 Jest 功能,以帮助您编写更快、更高效的测试。我们将介绍的主题包括:

Jest - 性能优化

优化 Jest 的性能对于大型项目或处理许多测试用例时非常重要。以下是一些可用于加速 Jest 测试的策略:

并行运行测试

默认情况下,Jest 并行运行测试,从而加快测试执行速度。它根据 CPU 核心数将测试划分为多个工作线程(进程),有助于减少测试运行时间。

您可以使用 ma​​xWorkers 选项在 Jest 配置文件(jest.config.js)中调整工作线程数量。

module.exports = {
    maxWorkers: "50%", // 使用 50% 的可用 CPU 核心
};

避免使用大型测试套件

对于大型测试套件,请通过将测试组织到单独的文件中或使用 testPathPattern 选项运行特定测试,将其拆分为较小的块。

jest --testPathPattern="specificTestFile"

避免缓慢的安装/拆卸

要优化缓慢的安装或拆卸(如数据库连接),请使用beforeAllafterAll以避免在beforeEachafterEach中重复操作。

使用jest.runAllTimers()jest.clearAllMocks()

要提高测试性能,请清除未使用的模拟计时器以防止运行不必要的代码。例如:

beforeEach(() => {
    jest.clearAllMocks(); // 清除不再需要的任何模拟
    jest.runAllTimers(); // 立即运行所有待处理的计时器
});

对 CI 环境使用 --silent 标志

在持续集成 (CI) 环境中,使用 --silent 标志可防止控制台中出现不必要的输出,这有助于减少测试执行期间的负载。

jest --silent

优化测试设置和拆卸

避免在 beforeAllafterAll 中进行繁重操作。尽量减少设置和拆卸,以加快执行速度。如果您的测试与数据库交互,请考虑模拟数据库以提高性能。

Jest - 自定义匹配器

Jest 中的自定义匹配器允许您创建自己的期望,使您的测试更清晰,更适合您的需求。它们对于测试特定行为或自定义数据结构特别有用。

创建自定义匹配器

要创建自定义匹配器,请使用 expect.extend()。以下是定义检查数字是否可以被另一个数字整除的匹配器的方法:

expect.extend({
    toBeDivisibleBy(received, argument) {
        const pass = received % argument === 0;
        if (pass) {
            return {
                message: () => `expected ${received} not to be divisible by ${argument}`,
                pass: true,
            };
        } else {
            return {
                message: () => `expected ${received} to be divisible by ${argument}`,
                pass: false,
            };
        }
    },
});

使用自定义匹配器

定义自定义匹配器后,您可以在测试中使用它,如下所示:

test('is divisible by 3', () => {
    expect(6).toBeDivisibleBy(3); // 传递
    expect(7).toBeDivisibleBy(3); // 失败
});

异步自定义匹配器

您还可以为涉及 Promise 异步操作的测试创建异步自定义匹配器。以下是异步匹配器的示例,用于检查从 URL 获取的数据是否包含特定值:

expect.extend({
    async toFetchData(received) {
        const response = await fetch(received);
        const data = await response.json();
        return {
            message: () => `expected ${received} to fetch data`,
            pass: data.someProperty === 'expectedValue',
        };
    },
});

Jest - 计时器模拟

计时器(例如 setTimeoutsetInterval)在 JavaScript 中很常见。Jest 提供了在测试中模拟和控制它们的工具,允许您测试与时间相关的函数,而无需等待实际时间流逝。

使用 Jest 模拟计时器

要在 Jest 中模拟计时器,请在测试开始时使用 jest.useFakeTimers()。这会将默认计时器替换为您可以控制的模拟计时器。

beforeEach(() => {
    jest.useFakeTimers(); // 启用假计时器
});

afterEach(() => {
    jest.useRealTimers(); // 每次测试后恢复真实计时器
});

推进计时器

一旦计时器被模拟,您可以使用 jest.advanceTimersByTime()jest.runAllTimers()

手动推进它们
  • jest.advanceTimersByTime(ms): 将计时器推进指定的毫秒数。
  • test('calls the callback after timeout', () => {
        const callback = jest.fn();
        setTimeout(callback, 1000);
    
        jest.advanceTimersByTime(1000); // Fast-forward 1000ms
        expect(callback).toHaveBeenCalled();
    });
        
    
  • jest.runAllTimers():运行所有待处理的计时器直至完成。
  • test('executes all timers', () => {
        const callback = jest.fn();
        setTimeout(callback, 500);
        setTimeout(callback, 1000);
    
        jest.runAllTimers(); // Executes both timeouts
        expect(callback).toHaveBeenCalledTimes(2);
    });
        
    

清除模拟计时器

为防止测试相互影响,请使用 jest.clearAllTimers() 清除模拟计时器,或在测试之间使用 jest.useRealTimers() 恢复原始计时器。

afterEach(() => {
    jest.clearAllTimers();
});

Jest - DOM 测试

当与 React Testing Library 或 Enzyme 等库一起使用时,Jest 非常适合DOM 测试。这些库使用 jsdom 模拟 Node.js 中的 DOM 环境。

使用 Jest 设置 DOM 测试

Jest 默认使用 jsdom,因此无需额外设置。要与 DOM 交互,请安装 React Testing LibraryEnzyme

要安装 React Testing Library:

npm install @testing-library/react @testing-library/jest-dom

编写 DOM 测试

设置完成后,您可以为组件编写测试。以下是如何在 React 组件中测试按钮点击的示例。

import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';

test('button click changes text', () => {
    render(<MyComponent />);
    const button = screen.getByRole('button');
    fireEvent.click(button);
    const text = screen.getByText(/button clicked/i);
    expect(text).toBeInTheDocument();
});

使用 Jest DOM 进行断言

jest-dom 为 Jest 添加了额外的匹配器,用于测试 DOM 元素。一些有用的匹配器是:

  • .toBeInTheDocument(): 检查元素是否在 DOM 中。
  • .toHaveTextContent(): 验证元素是否包含给定的文本。
  • .toBeVisible(): 确保元素在屏幕上可见。

示例

import '@testing-library/jest-dom'; // Provides extra matchers

test('button displays correct text', () => {
    render(<MyComponent />);
    const button = screen.getByRole('button');
    expect(button).toHaveTextContent('Click me');
    expect(button).toBeVisible();
});

测试后清理

Jest 会在每次测试后自动清理 DOM。但是,如果需要,您也可以使用 React Testing Library 中的 cleanup() 函数手动清除 DOM。

示例

import { cleanup } from '@testing-library/react';

afterEach(() => {
    jest.clearAllMocks();
    cleanup();
});

这将在每次测试后清除 DOM,以防止测试之间的干扰。