Jest - 最佳实践

在本章中,我们将介绍使用 Jest 编写更快、更可靠的测试的最佳实践要避免的常见错误以及如何组织测试文件。通过遵循这些做法,您将保持测试的可维护性和高效性。

Jest - 测试最佳实践

以下是使用 Jest 编写清晰、高效和可靠的测试的一些最佳实践:

  • 保持测试小而有重点:每个测试都应关注单一行为。这使得理解和快速识别错误变得更加容易。
  • 使用描述性测试名称:为您的测试提供清晰、描述性的名称,以解释它们的作用。这有助于其他人了解正在测试的内容以及预期结果。
  • 测试行为,而不是实现:关注代码的作用,而不是它如何执行。这样,即使代码发生变化,您的测试仍然有用。
  • 正确使用设置和拆卸:使用beforeEachafterEach在测试前后进行设置和清理。这有助于保持测试隔离并确保每个测试都处于干净的状态。
  • 保持测试独立:每个测试都应该独立。一个测试不应依赖于另一个测试的结果。这使测试更可靠,更容易排除故障。
  • 测试边缘情况:测试您的代码如何处理意外或极端的输入,例如空值、大数字或无效数据。这可确保您的代码在所有情况下都能正常工作。
  • 使用 Mocks 和 Spies:使用 mocksspies 隔离您正在测试的代码,尤其是在测试与外部系统(如 API 或数据库)交互的函数时。这可使测试快速而集中。
  • 避免测试私有方法:不要直接测试私有方法。相反,专注于测试代码的公共行为。这可使测试更加灵活,并且更少地依赖于实现细节。
  • 对异步代码使用 async/await:对于 async 代码,使用 async/await 确保您的测试在检查结果之前等待承诺解决。这使测试更易于阅读且不易出错。
  • 并行运行测试:Jest 默认并行运行测试,从而加快测试速度。确保您的测试不依赖于共享状态,这样它们就可以独立且更快地运行。

Jest - 应避免的常见错误

Jest 是一个强大的测试工具,但有一些常见错误可能会使您的测试不可靠。以下是一些常见错误及其避免方法:

未清理模拟

使用模拟时,每次测试后清理它们非常重要,以避免内存泄漏或测试之间的干扰。

解决方案:在 afterEach 块内使用 jest.clearAllMocks()jest.resetAllMocks()

afterEach(() => {
    jest.clearAllMocks(); // 每次测试后重置模拟
});

在测试中过度使用 setTimeout 或 setInterval

使用 setTimeoutsetInterval 会减慢测试速度并使其不可靠,尤其是在使用异步代码时。

解决方案:使用 jest.useFakeTimers() 模拟计时器并控制测试中的时间流。

jest.useFakeTimers(); // 模拟计时器以加快测试速度

未对异步测试使用 async/await

测试异步代码时始终使用 async/await。避免使用回调,因为它们可能会导致处理异步行为的问题。

解决方案:使用异步函数或从测试中返回承诺。

test('should fetch data correct', async () => {
    const data = await fetchData();
    expect(data).toBeDefined(); // 确保已获取数据
});

未处理异步测试中的错误

测试异步代码时,始终处理可能的错误。如果不处理错误,则可能导致测试意外失败。

解决方案:使用try/catch.catch()处理异步测试中的错误。

test('should throw an error if data is invalid', async () => {
    try {
        await fetchData();
    } catch (e) {
        expect(e).toBeDefined();  // Check for errors
    }
});

未使用正确的断言

断言对于检查预期结果是否与实际输出匹配非常重要。使用正确的断言方法来验证测试结果。

解决方案:正确使用 Jest 的断言 方法(如 toBetoBeTruthytoHaveLength)。

expect(result).toBe(true); // 检查是否严格相等
expect(result).toBeTruthy(); // 检查是否真实
expect(result).toHaveLength(3); // 检查数组长度

使测试过于复杂

测试应该简单易懂。避免在一个测试用例中测试太多内容或使用太多断言。

解决方案:将复杂的逻辑分解为更小、更易于管理的测试。

// Bad practice
test('should process data and render components correctly', () => {
    const result = processData();
    render(<Component data={result} />);
    expect(screen.getByText('Some Data')).toBeInTheDocument();
});

// 更好的做法:分成多个测试
test('should process data correctly', () => {
    const result = processData();
    expect(result).toBeDefined();  // 单独测试数据处理
});

test('should render component with processed data', () => {
    render(<Component data={processedData} />);
    expect(screen.getByText('Some Data')).toBeInTheDocument();  // 单独测试渲染
});

忽略测试覆盖率

测试覆盖率并不能保证无错误代码,但跟踪代码的哪些部分正在测试非常重要。

解决方案:使用 Jest 的 --coverage 选项检查代码的哪些部分被测试覆盖。

jest --coverage // 运行带有覆盖率报告的测试

确保测试所有重要路径(包括边缘情况)。

Jest - 代码组织

组织好 Jest 测试有助于使代码更易于维护和扩展。良好的组织结构可让您轻松查找、更新和运行测试。

按功能组织测试

将相关功能的测试分组到各自的文件夹中。这样,当您更改功能时,可以更轻松地查找和更新测试。

组织测试功能

使用 __tests__ 文件夹存放测试文件

您可以为所有测试文件创建单独的 __tests__ 文件夹,而不是将测试放在代码旁边。这可让您的项目更整洁。

Src 文件夹概述

测试文件的命名约定

为测试文件使用清晰的名称。常见的惯例是使用 .test.js.spec.js 作为测试文件(例如 Card.test.jsHeader.spec.js)。

模拟和存根

如果您要模拟 API 或数据库等依赖项,请将它们放在 __mocks__ 文件夹中。这样,您可以在多个测试中重复使用模拟。

mocks outline

对于仅在单个测试中使用的模拟,您可以在该测试中直接模拟它们。

jest.mock('axios');

将测试放在靠近代码的位置

将测试文件放在靠近测试代码的位置。这样可以更轻松地同时更新代码和测试。如果愿意,您还可以使用 __tests__ 文件夹来存储测试。

使用 describe 对测试进行分组

使用 describe 块将相关测试分组在一起。这样可以清楚地了解正在测试的代码部分。

describe('User authentication', () => {
    test('should log in with valid credentials', () => {
        expect(login('user', 'password')).toBe(true);
    });

    test('should reject invalid credentials', () => {
        expect(login('user', 'wrong-password')).toBe(false);
    });
});

确保一致的测试设置

使用 Jest 的设置函数(如 beforeAllafterAllbeforeEachafterEach)确保所有测试都具有相同的环境设置。

beforeEach(() => {
    jest.resetModules(); // Reset module cache before each test
});