mcpField
T
testing-patterns

testing-patterns

github.com/davila7测试与质量33.0k306 次安装今天更新
代码仓库

关于 testing-patterns

面向使用 React Native 与 Jest 的开发者的测试模式参考,强调先写失败测试、验证行为而非实现,并用工厂函数和自定义渲染工具保持测试一致。

主要功能

  1. 01采用测试驱动开发流程
  2. 02优先验证行为与公共 API
  3. 03用工厂函数构造测试数据
  4. 04封装组件渲染所需 Provider
  5. 05提供模块与 GraphQL Hook 模拟模式
  6. 06覆盖异步查询和用户交互

适用场景

  1. 01为组件创建默认属性与数据工厂
  2. 02测试带 Provider 的 React Native 界面
  3. 03验证 GraphQL 查询及异步状态
  4. 04编写表单交互和行为测试

安装

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

$npx skills add https://github.com/davila7/claude-code-templates --skill testing-patterns

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

详细介绍

测试模式与工具

测试理念

测试驱动开发(TDD):

  • 首先编写失败的测试
  • 实现足以通过测试的最少代码
  • 测试通过后再重构
  • 没有失败的测试时,绝不编写生产代码

行为驱动测试:

  • 测试行为,而非实现
  • 聚焦于公共 API 和业务需求
  • 避免测试实现细节
  • 使用能描述行为的测试名称

工厂模式:

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

测试工具

自定义渲染函数

创建自定义渲染函数,为组件包装所需的 Provider:

// 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('should render component with default props', () => {});
    it('should render loading state when loading', () => {});
  });

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

  describe('Edge cases', () => {
    it('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
await waitFor(() => {
  expect(screen.findByText('Loaded')).toBeTruthy();
});

用户交互模式

import { fireEvent, screen } 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 前,先编写能够复现该 Bug 的测试

评论

支持 Markdown