mcpField
T
testing-patterns

testing-patterns

github.com/sickn33测试与质量48.7k1.1k 次安装今天更新
代码仓库

关于 testing-patterns

为使用 Jest 编写单元测试的开发者提供测试模式与工具建议,涵盖 TDD、行为测试、工厂函数和模拟实践。

主要功能

  1. 01TDD 红绿重构流程
  2. 02关注行为与公开 API
  3. 03测试数据和属性工厂
  4. 04模块及 GraphQL Hook 模拟
  5. 05组件交互与查询模式

适用场景

  1. 01为 React Native 组件编写测试
  2. 02用工厂函数生成可复用测试数据
  3. 03通过失败测试复现并修复问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill testing-patterns

安装后在 Claude Code 中输入/testing-patterns确认已加载。

详细介绍

测试模式与实用工具

测试理念

测试驱动开发(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('should render component', () => {
  renderWithTheme(<MyComponent />);
  expect(screen.getByText('Hello')).toBeTruthy();
});

工厂模式

组件 Props 工厂
import { ComponentProps } from 'react';

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

// Usage in tests
it('should render with custom title', () => {
  const props = getMockMyComponentProps({ title: 'Custom Title' });
  renderWithTheme(<MyComponent {...props} />);
  expect(screen.getByText('Custom Title')).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: '[email protected]',
    role: 'user',
    ...overrides,
  };
};

// Usage
it('should display admin badge for admin users', () => {
  const user = getMockUser({ role: 'admin' });
  renderWithTheme(<UserCard user={user} />);
  expect(screen.getByText('Admin')).toBeTruthy();
});

模拟模式

模拟模块
// Mock entire module
jest.mock('utils/analytics');

// Mock with factory function
jest.mock('utils/analytics', () => ({
  Analytics: {
    logEvent: jest.fn(),
  },
}));

// Access mock in test
const mockLogEvent = jest.requireMock('utils/analytics').Analytics.logEvent;
模拟 GraphQL Hooks
jest.mock('./GetItems.generated', () => ({
  useGetItemsQuery: jest.fn(),
}));

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

// In test
mockUseGetItemsQuery.mockReturnValue({
  data: { items: [] },
  loading: false,
  error: undefined,
});

测试结构

describe('ComponentName', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Rendering', () => {
    it.todo('should render component with default props');
    it.todo('should render loading state when loading');
  });

  describe('User interactions', () => {
    it.todo('should call onPress when button is clicked');
  });

  describe('Edge cases', () => {
    it.todo('should handle empty data gracefully');
  });
});

查询模式

// Element must exist
expect(screen.getByText('Hello')).toBeTruthy();

// Element should not exist
expect(screen.queryByText('Goodbye')).toBeNull();

// Element appears asynchronously
// async-appearance-example: await the element, not the Promise
expect(await screen.findByText('Loaded')).toBeTruthy();

用户交互模式

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

it('should submit form on button click', async () => {
  const onSubmit = jest.fn();
  renderWithTheme(<LoginForm onSubmit={onSubmit} />);

  fireEvent.changeText(screen.getByLabelText('Email'), '[email protected]');
  fireEvent.changeText(screen.getByLabelText('Password'), 'password123');
  fireEvent.press(screen.getByTestId('login-button'));

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

应避免的反模式

测试模拟行为而非真实行为
// Bad - testing the mock
expect(mockFetchData).toHaveBeenCalled();

// Good - testing actual behavior
expect(screen.getByText('John Doe')).toBeTruthy();
不使用工厂
// Bad - duplicated, inconsistent test data
it('test 1', () => {
  const user = { id: '1', name: 'John', email: '[email protected]', role: 'user' };
});
it('test 2', () => {
  const user = { id: '2', name: 'Jane', email: '[email protected]' }; // Missing role!
});

// Good - reusable factory
const user = getMockUser({ name: 'Custom Name' });

最佳实践

  1. 始终使用工厂函数创建 Props 和数据
  2. 测试行为,而非实现
  3. 使用说明性测试名称
  4. 使用 describe 块组织测试
  5. 在测试之间清除模拟
  6. 让测试聚焦——每个测试只验证一种行为

运行测试

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Run specific file
npm test ComponentName.test.tsx

与其他技能的集成

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

适用场景

编写 Jest 单元测试、测试工厂和模拟,或通过红—绿—重构周期诊断失败的测试时使用。

局限性

  • 仅当任务明确符合上述范围时,才使用此技能。
  • 不要将输出视为替代针对特定环境的验证、测试或专家审查。
  • 如果缺少所需输入、权限、安全边界或成功标准,请停止并请求澄清。

评论

支持 Markdown