测试模式Skill testing-patterns

这个技能提供了使用Jest进行软件测试的模式、工厂函数、模拟策略和测试驱动开发工作流的详细指南。它适用于编写单元测试、创建可重用的测试工厂,并遵循行为驱动测试原则。关键词:测试、单元测试、Jest、工厂函数、模拟、TDD、行为驱动测试、软件测试。

测试 0 次安装 0 次浏览 更新于 3/21/2026

name: 测试模式 description: Jest测试模式、工厂函数、模拟策略和TDD工作流。用于编写单元测试、创建测试工厂或遵循TDD红绿重构周期。

测试模式与工具

测试理念

测试驱动开发 (TDD):

  • 首先编写失败的测试
  • 实现最小代码以通过测试
  • 在绿色后重构
  • 从不编写生产代码而没有失败的测试

行为驱动测试:

  • 测试行为,而不是实现
  • 关注公共API和业务需求
  • 避免测试实现细节
  • 使用描述行为的描述性测试名称

工厂模式:

  • 创建 getMockX(overrides?: Partial<X>) 函数
  • 提供合理的默认值
  • 允许覆盖特定属性
  • 保持测试DRY和可维护

测试工具

自定义渲染函数

创建一个自定义渲染,包装组件与所需的提供者:

// src/utils/testUtils.tsx
import { render } from '@testing-library/react-native';
import { ThemeProvider } from './theme';

export const renderWithTheme = (ui: React.ReactElement) => {
  return render(
    <ThemeProvider>{ui}</ThemeProvider>
  );
};

用法:

import { renderWithTheme } from 'utils/testUtils';
import { screen } from '@testing-library/react-native';

it('应该渲染组件', () => {
  renderWithTheme(<MyComponent />);
  expect(screen.getByText('Hello')).toBeTruthy();
});

工厂模式

组件属性工厂

import { ComponentProps } from 'react';

const getMockMyComponentProps = (
  overrides?: Partial<ComponentProps<typeof MyComponent>>
) => {
  return {
    title: '默认标题',
    count: 0,
    onPress: jest.fn(),
    isLoading: false,
    ...overrides,
  };
};

// 在测试中的用法
it('应该渲染自定义标题', () => {
  const props = getMockMyComponentProps({ title: '自定义标题' });
  renderWithTheme(<MyComponent {...props} />);
  expect(screen.getByText('自定义标题')).toBeTruthy();
});

数据工厂

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

const getMockUser = (overrides?: Partial<User>): User => {
  return {
    id: '123',
    name: 'John Doe',
    email: 'john@example.com',
    role: 'user',
    ...overrides,
  };
};

// 用法
it('应该为管理员用户显示管理员徽章', () => {
  const user = getMockUser({ role: 'admin' });
  renderWithTheme(<UserCard user={user} />);
  expect(screen.getByText('Admin')).toBeTruthy();
});

模拟模式

模拟模块

// 模拟整个模块
jest.mock('utils/analytics');

// 使用工厂函数模拟
jest.mock('utils/analytics', () => ({
  Analytics: {
    logEvent: jest.fn(),
  },
}));

// 在测试中访问模拟
const mockLogEvent = jest.requireMock('utils/analytics').Analytics.logEvent;

模拟GraphQL钩子

jest.mock('./GetItems.generated', () => ({
  useGetItemsQuery: jest.fn(),
}));

const mockUseGetItemsQuery = jest.requireMock(
  './GetItems.generated'
).useGetItemsQuery as jest.Mock;

// 在测试中
mockUseGetItemsQuery.mockReturnValue({
  data: { items: [] },
  loading: false,
  error: undefined,
});

测试结构

describe('组件名称', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('渲染', () => {
    it('应该用默认属性渲染组件', () => {});
    it('应该在加载时渲染加载状态', () => {});
  });

  describe('用户交互', () => {
    it('点击按钮时应调用onPress', async () => {});
  });

  describe('边缘情况', () => {
    it('应该优雅地处理空数据', () => {});
  });
});

查询模式

// 元素必须存在
expect(screen.getByText('Hello')).toBeTruthy();

// 元素不应存在
expect(screen.queryByText('Goodbye')).toBeNull();

// 元素异步出现
await waitFor(() => {
  expect(screen.findByText('Loaded')).toBeTruthy();
});

用户交互模式

import { fireEvent, screen } from '@testing-library/react-native';

it('点击按钮时应提交表单', async () => {
  const onSubmit = jest.fn();
  renderWithTheme(<LoginForm onSubmit={onSubmit} />);

  fireEvent.changeText(screen.getByLabelText('Email'), 'user@example.com');
  fireEvent.changeText(screen.getByLabelText('Password'), 'password123');
  fireEvent.press(screen.getByTestId('login-button'));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalled();
  });
});

要避免的反模式

测试模拟行为而不是实际行为

// 坏 - 测试模拟
expect(mockFetchData).toHaveBeenCalled();

// 好 - 测试实际行为
expect(screen.getByText('John Doe')).toBeTruthy();

不使用工厂函数

// 坏 - 重复、不一致的测试数据
it('测试1', () => {
  const user = { id: '1', name: 'John', email: 'john@test.com', role: 'user' };
});
it('测试2', () => {
  const user = { id: '2', name: 'Jane', email: 'jane@test.com' }; // 缺少角色!
});

// 好 - 可重用的工厂
const user = getMockUser({ name: '自定义名称' });

最佳实践

  1. 始终使用工厂函数 用于属性和数据
  2. 测试行为,而不是实现
  3. 使用描述性测试名称
  4. 使用描述块组织
  5. 在测试间清除模拟
  6. 保持测试专注 - 每个测试一个行为

运行测试

# 运行所有测试
npm test

# 带覆盖率运行
npm run test:coverage

# 运行特定文件
npm test ComponentName.test.tsx

与其他技能集成

  • react-ui-patterns: 测试所有UI状态(加载、错误、空、成功)
  • systematic-debugging: 修复前编写重现错误的测试