12. React 应用的测试环节

3 阅读4分钟

React 应用的测试环节

测试 React 应用 是确保 代码质量稳定性 的重要环节。测试 React 应用时,通常会涉及以下几个方面:

  1. 单元测试(Unit Testing)
  2. 集成测试(Integration Testing)
  3. 端到端测试(End-to-End Testing)
  4. 性能测试(Performance Testing)

1 React 测试工具

React 提供了一些 非常流行 和 强大的 测试工具,包括:

  • Jest:一个流行的 JavaScript 测试框架React 官方推荐 的 测试工具。它自带了 模拟(mocking)、断言(assertions)等功能;
  • React Testing Library:一个专注于 测试 React 组件工具库,它更注重 以用户的角度 进行测试,避免 直接操作 组件实例
  • Enzyme:一个用于 React 组件的 测试工具,提供了 多种 API模拟 和 操作组件,虽然 React Testing Library 越来越流行,但 Enzyme 依然是一些 老项目中 的常见选择。

2 设置 Jest 和 React Testing Library

React 项目 默认使用 Create React App 创建 时,已经包含了 JestReact Testing Library。你可以直接开始 编写测试

3 编写 和 运行测试

3.1 单元测试(Unit Testing)

单元测试 通常用于测试 独立的 功能单元React 组件。你可以 模拟 一些行为检查 组件渲染后的输出 是否符合预期

1 测试一个简单的组件渲染

假设我们有一个 Greeting 组件,它根据 name 属性 显示 欢迎信息。

// Greeting.js
import React from 'react';

function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

export default Greeting;

你可以 为这个组件 编写一个 单元测试,检查 它 是否正确渲染:

// Greeting.test.js
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';

test('renders a greeting message with name', () => {
  render(<Greeting name="Alice" />);
  const greetingElement = screen.getByText(/Hello, Alice!/);
  expect(greetingElement).toBeInTheDocument();
});
2 事件 和 交互测试

除了 渲染测试,你还可以 模拟用户交互 并 验证 它们的效果。

例如,模拟 点击事件 并检查 应用状态的变化。假设你有一个按钮,点击时会改变组件的状态:

// Counter.js
import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default Counter;

你可以 编写测试 来检查 按钮点击 是否正确更新 计数器:

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

test('increments counter when button is clicked', () => {
  render(<Counter />);

  const button = screen.getByText(/Click me/);
  fireEvent.click(button);
  const counter = screen.getByText(/You clicked 1 times/);
  expect(counter).toBeInTheDocument();
});
3 Mocking 和 模拟依赖

有时你需要 模拟外部依赖(例如 API 请求)。jest 提供了非常强大的 mocking 功能,让你能够 模拟 组件的 依赖行为

假设你有一个从 API 获取数据的组件:

// User.js
import React, { useEffect, useState } from 'react';

function User() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/user')
      .then((response) => response.json())
      .then((data) => setUser(data));
  }, []);

  if (!user) {
    return <div>Loading...</div>;
  }

  return <div>Hello, {user.name}!</div>;
}

export default User;

在测试中,你可以 模拟 fetch 请求,而不实际发送网络请求:

// User.test.js
import { render, screen, waitFor } from '@testing-library/react';
import User from './User';

test('fetches and displays user data', async () => {
  // Mock fetch API
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ name: 'Alice' }),
    })
  );

  render(<User />);

  // Assert loading state
  expect(screen.getByText(/Loading.../)).toBeInTheDocument();

  // Wait for the data to be fetched and rendered
  await waitFor(() => screen.getByText(/Hello, Alice!/));

  // Assert the user name is displayed
  expect(screen.getByText(/Hello, Alice!/)).toBeInTheDocument();
});
4 快照测试(Snapshot Testing)

快照测试 用于确保 组件的输出 没有不经意的变化。Jest 会生成 组件渲染的 快照,并与 上次的快照 进行对比,如果有差异,则会报告出来。

// Button.js
import React from 'react';

function Button({ label }) {
  return <button>{label}</button>;
}

export default Button;
// Button.test.js
import { render } from '@testing-library/react';
import Button from './Button';

test('Button renders correctly', () => {
  const { asFragment } = render(<Button label="Click Me" />);
  expect(asFragment()).toMatchSnapshot();
});

3.2 集成测试(Integration Testing)

集成测试 主要用于 测试 不同组件 之间的 交互。通过 组合多个组件 进行测试,可以确保它们 一起工作时 不会发生 意外的错误。

示例

假设你有一个 表单组件,提交时 会将数据 发送到 父组件。

// Form.js
import React, { useState } from 'react';

function Form({ onSubmit }) {
  const [input, setInput] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(input);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export default Form;

你可以编写 集成测试 来验证 表单提交功能 是否正常工作:

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

test('submits form with input data', () => {
  const onSubmit = jest.fn();
  render(<Form onSubmit={onSubmit} />);

  const input = screen.getByRole('textbox');
  const button = screen.getByRole('button', { name: /submit/i });

  fireEvent.change(input, { target: { value: 'Hello' } });
  fireEvent.click(button);

  expect(onSubmit).toHaveBeenCalledWith('Hello');
});

3.3 端到端测试(End-to-End Testing)

端到端测试 模拟用户整个操作流程,验证应用从 前端后端工作流程。常用的 端到端测试工具 有:

  • Cypress:一个非常流行的 端到端测试工具,提供了良好的开发者体验,支持 实时调试自动化测试
  • Puppeteer:一个 Node 库,用于 控制 Chrome 或 Chromium 浏览器,适用于 自动化操作 和 截图测试
  • Playwright:一个 Microsoft 提供的端到端测试工具,类似于 Puppeteer,支持更多浏览器。
示例(Cypress)
npm install cypress --save-dev

创建一个简单的端到端测试,模拟用户登录操作:

// cypress/integration/login_spec.js
describe('Login Page', () => {
  it('should log in successfully', () => {
    cy.visit('/login');
    cy.get('input[name="username"]').type('user');
    cy.get('input[name="password"]').type('password');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});