关于 testing-patterns
为使用 Jest 编写单元测试的开发者提供测试模式与工具建议,涵盖 TDD、行为测试、工厂函数和模拟实践。
主要功能
- 01TDD 红绿重构流程
- 02关注行为与公开 API
- 03测试数据和属性工厂
- 04模块及 GraphQL Hook 模拟
- 05组件交互与查询模式
适用场景
- 01为 React Native 组件编写测试
- 02用工厂函数生成可复用测试数据
- 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' });
最佳实践
- 始终使用工厂函数创建 Props 和数据
- 测试行为,而非实现
- 使用说明性测试名称
- 使用 describe 块组织测试
- 在测试之间清除模拟
- 让测试聚焦——每个测试只验证一种行为
运行测试
# 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