Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/davila7-testing-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 31% | 0% |
Test-Driven Development (TDD):
Behavior-Driven Testing:
Factory Pattern:
getMockX(overrides?: Partial<X>) functionsCreate a custom render that wraps components with required providers:
typescript// 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> ); };
Usage:
typescriptimport { renderWithTheme } from 'utils/testUtils'; import { screen } from '@testing-library/react-native'; it('should render component', () => { renderWithTheme(<MyComponent />); expect(screen.getByText('Hello')).toBeTruthy(); });
typescriptimport { 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(); });
typescriptinterface User { id: string; name: string; email: string; role: 'admin' | 'user'; } const getMockUser = (overrides?: Partial<User>): User => { return { id: '123', name: 'John Doe', email: 'john@example.com', 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(); });
typescript// 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;
typescriptjest.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, });
typescriptdescribe('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', () => {}); }); });
typescript// 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(); });
typescriptimport { 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'), 'user@example.com'); fireEvent.changeText(screen.getByLabelText('Password'), 'password123'); fireEvent.press(screen.getByTestId('login-button')); await waitFor(() => { expect(onSubmit).toHaveBeenCalled(); }); });
typescript// Bad - testing the mock expect(mockFetchData).toHaveBeenCalled(); // Good - testing actual behavior expect(screen.getByText('John Doe')).toBeTruthy();
typescript// Bad - duplicated, inconsistent test data it('test 1', () => { const user = { id: '1', name: 'John', email: 'john@test.com', role: 'user' }; }); it('test 2', () => { const user = { id: '2', name: 'Jane', email: 'jane@test.com' }; // Missing role! }); // Good - reusable factory const user = getMockUser({ name: 'Custom Name' });
bash# Run all tests npm test # Run with coverage npm run test:coverage # Run specific file npm test ComponentName.test.tsx
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 7,719 | 8,395 | +9% | 1 | 1 | 0% | 1,371 | 3,034 | +121% | 0 | 0 | — |
case-06 | pass→pass | 12,126 | 8,726 | -28% | 1 | 1 | 0% | 2,125 | 3,226 | +52% | 0 | 0 | — |
case-07 | fail→pass | 12,982 | 7,827 | -40% | 1 | 1 | 0% | 2,467 | 3,037 | +23% | 0 | 0 | — |
case-08 | fail→fail | 11,026 | 7,018 | -36% | 1 | 1 | 0% | 1,929 | 2,906 | +51% | 0 | 0 | — |
case-09 | pass→pass | 10,954 | 8,379 | -24% | 1 | 1 | 0% | 2,243 | 3,126 | +39% | 0 | 0 | — |
case-15 | fail→pass | 15,317 | 11,508 | -25% | 1 | 1 | 0% | 2,901 | 3,773 | +30% | 0 | 0 | — |
case-01 | fail→fail | 15,337 | 10,233 | -33% | 1 | 1 | 0% | 3,227 | 3,631 | +13% | 0 | 0 | — |
case-02 | fail→pass | 12,219 | 10,357 | -15% | 1 | 1 | 0% | 2,598 | 3,684 | +42% | 0 | 0 | — |
case-03 | fail→pass | 15,920 | 11,687 | -27% | 1 | 1 | 0% | 2,955 | 3,665 | +24% | 0 | 0 | — |
case-04 | pass→pass | 8,709 | 8,955 | +3% | 1 | 1 | 0% | 1,858 | 3,354 | +81% | 0 | 0 | — |
case-10 | pass→pass | 8,871 | 6,993 | -21% | 1 | 1 | 0% | 1,609 | 2,849 | +77% | 0 | 0 | — |
case-11 | pass→pass | 9,534 | 8,018 | -16% | 1 | 1 | 0% | 1,970 | 3,183 | +62% | 0 | 0 | — |
case-12 | fail→pass | 10,210 | 6,310 | -38% | 1 | 1 | 0% | 2,112 | 2,768 | +31% | 0 | 0 | — |
case-13 | pass→pass | 11,166 | 6,629 | -41% | 1 | 1 | 0% | 1,924 | 2,549 | +32% | 0 | 0 | — |
case-14 | pass→pass | 10,891 | 6,158 | -43% | 1 | 1 | 0% | 1,695 | 2,483 | +46% | 0 | 0 | — |
case-16 | fail→pass | 5,523 | 3,476 | -37% | 1 | 1 | 0% | 1,019 | 2,303 | +126% | 0 | 0 | — |
case-17 | pass→pass | 8,907 | 6,327 | -29% | 1 | 1 | 0% | 1,638 | 2,589 | +58% | 0 | 0 | — |
case-18 | fail→pass | 12,235 | 8,801 | -28% | 1 | 1 | 0% | 1,901 | 3,073 | +62% | 0 | 0 | — |
case-19 | pass→pass | 14,305 | 11,655 | -19% | 1 | 1 | 0% | 2,522 | 3,565 | +41% | 0 | 0 | — |
case-20 | pass→pass | 4,033 | 2,063 | -49% | 1 | 1 | 0% | 586 | 1,771 | +202% | 0 | 0 | — |
case-21 | pass→pass | 6,369 | 1,621 | -75% | 1 | 1 | 0% | 1,077 | 1,708 | +59% | 0 | 0 | — |
case-22 | pass→pass | 18,061 | 10,880 | -40% | 1 | 1 | 0% | 2,849 | 3,442 | +21% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.