
name: testing-patternsdescription: “Jest testing patterns, factory functions, mocking strategies, and TDD workflow. Use when writing unit tests, creating test factories, or following TDD red-green-refactor cycle.”risk: criticalsource: communitydate_added: “2026-02-27”测试模式与工具测试理念测试驱动开发TDD先编写失败的测试实现最少的代码来通过变绿后重构没有失败的测试绝不编写生产代码行为驱动测试测试行为而非实现关注公共 API 和业务需求避免测试实现细节使用描述行为的描述性测试名称工厂模式创建getMockX(overrides?: PartialX)函数提供合理的默认值允许覆盖特定属性保持测试 DRY 且可维护测试工具自定义渲染函数创建用所需提供程序包装组件的自定义渲染// src/utils/testUtils.tsximport{render}fromtesting-library/react-native;import{ThemeProvider}from./theme;exportconstrenderWithTheme(ui:React.ReactElement){returnrender(ThemeProvider{ui}/ThemeProvider);};用法import{renderWithTheme}fromutils/testUtils;import{screen}fromtesting-library/react-native;it(should render component,(){renderWithTheme(MyComponent/);expect(screen.getByText(Hello)).toBeTruthy();});工厂模式组件属性工厂import{ComponentProps}fromreact;constgetMockMyComponentProps(overrides?:PartialComponentPropstypeofMyComponent){return{title:Default Title,count:0,onPress:jest.fn(),isLoading:false,...overrides,};};// Usage in testsit(should render with custom title,(){constpropsgetMockMyComponentProps({title:Custom Title});renderWithTheme(MyComponent{...props}/);expect(screen.getByText(Custom Title)).toBeTruthy();});数据工厂interfaceUser{id:string;name:string;email:string;role:admin|user;}constgetMockUser(overrides?:PartialUser):User{return{id:123,name:John Doe,email:johnexample.com,role:user,...overrides,};};// Usageit(should display admin badge for admin users,(){constusergetMockUser({role:admin});renderWithTheme(UserCard user{user}/);expect(screen.getByText(Admin)).toBeTruthy();});Mock 模式Mock 模块// Mock entire modulejest.mock(utils/analytics);// Mock with factory functionjest.mock(utils/analytics,()({Analytics:{logEvent:jest.fn(),},}));// Access mock in testconstmockLogEventjest.requireMock(utils/analytics).Analytics.logEvent;Mock GraphQL 钩子jest.mock(./GetItems.generated,()({useGetItemsQuery:jest.fn(),}));constmockUseGetItemsQueryjest.requireMock(./GetItems.generated).useGetItemsQueryasjest.Mock;// In testmockUseGetItemsQuery.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 existexpect(screen.getByText(Hello)).toBeTruthy();// Element should not existexpect(screen.queryByText(Goodbye)).toBeNull();// Element appears asynchronouslyawaitwaitFor((){expect(screen.findByText(Loaded)).toBeTruthy();});用户交互模式import{fireEvent,screen}fromtesting-library/react-native;it(should submit form on button click,async(){constonSubmitjest.fn();renderWithTheme(LoginForm onSubmit{onSubmit}/);fireEvent.changeText(screen.getByLabelText(Email),userexample.com);fireEvent.changeText(screen.getByLabelText(Password),password123);fireEvent.press(screen.getByTestId(login-button));awaitwaitFor((){expect(onSubmit).toHaveBeenCalled();});});应避免的反模式测试 Mock 行为而非真实行为// Bad - testing the mockexpect(mockFetchData).toHaveBeenCalled();// Good - testing actual behaviorexpect(screen.getByText(John Doe)).toBeTruthy();不使用工厂// Bad - duplicated, inconsistent test datait(test 1,(){constuser{id:1,name:John,email:johntest.com,role:user};});it(test 2,(){constuser{id:2,name:Jane,email:janetest.com};// Missing role!});// Good - reusable factoryconstusergetMockUser({name:Custom Name});最佳实践对属性和数据始终使用工厂函数测试行为而非实现使用描述性测试名称用 describe 块组织测试之间清除 mock保持测试聚焦- 每个测试一种行为运行测试# Run all testsnpmtest# Run with coveragenpmrun test:coverage# Run specific filenpmtestComponentName.test.tsx与其他技能的集成react-ui-patterns测试所有 UI 状态加载、错误、空、成功systematic-debugging修复之前编写复现 bug 的测试何时使用此技能适用于执行概述中描述的工作流或操作。局限性仅当任务明确符合上述范围时使用此技能。不要将输出视为环境特定验证、测试或专家审查的替代品。如果缺少必需的输入、权限、安全边界或成功标准请停下来询问澄清。