mcpField
T
testing-patterns

testing-patterns

github.com/chriswiles测试与质量6.1k83 次安装43 周前更新
代码仓库

关于 testing-patterns

Testing Patterns and Utilities 汇集 React Native 组件测试的实践与工具,面向编写和维护测试的开发者。内容包括测试优先、工厂函数、模块模拟、用户交互及界面状态测试。

主要功能

  1. 01测试驱动与行为测试原则
  2. 02带 Provider 的自定义渲染
  3. 03属性和数据工厂函数
  4. 04模块与 GraphQL Hook 模拟
  5. 05按组件行为组织测试
  6. 06查询和用户交互测试

适用场景

  1. 01为组件配置带主题的测试渲染
  2. 02用工厂函数复用测试数据
  3. 03模拟 GraphQL 查询并验证界面行为
  4. 04测试表单提交和异步显示

安装

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

$npx skills add https://github.com/chriswiles/claude-code-showcase --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