testing-patterns
Provides concrete Jest testing patterns for React Native, including factory functions for props/data, custom render utilities, mocking strategies for modules/GraphQL, and structured test organization. Focuses on behavior-driven testing with clear anti-patterns to avoid.
Output Preview
// UserProfile.test.tsx import { renderWithTheme, screen, fireEvent, waitFor } from 'utils/testUtils'; import { getMockUser } from 'utils/testFactories'; import UserProfile from './UserProfile'; // Mock external dependencies jest.mock('utils/analytics', () => ({ Analytics: { logEvent: jest.fn(), }, })); const mockLogEvent = jest.requireMock('utils/analytics').Analytics.logEvent; describe('UserProfile', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('Rendering', () => { it('should display user information correctly', () => { // Use factory pattern for consistent test data const user = getMockUser({ name: 'Alex Johnson', role: 'admin', email: 'alex@company.com' }); renderWithTheme(<UserProfile user={user} />); // Test actual behavior, not implementation expect(screen.getByText('Alex Johnson')).toBeTruthy(); expect(screen.getByText('Admin')).toBeTruthy(); expect(screen.queryByText('User')).toBeNull(); // Should not show 'User' for admin }); it('should show loading state when data is fetching', () => { renderWithTheme(<UserProfile user={null} isLoading={true} />); expect(screen.getByTestId('loading-spinner')).toBeTruthy(); expect(screen.queryByText('Alex Johnson')).toBeNull(); }); }); describe('User interactions', () => { it('should call onEdit when edit button is pressed', async () => { const onEdit = jest.fn(); const user = getMockUser(); renderWithTheme(<UserProfile user={user} onEdit={onEdit} />); fireEvent.press(screen.getByTestId('edit-profile-button')); await waitFor(() => { expect(onEdit).toHaveBeenCalledWith(user.id); // Verify analytics was called as side effect expect(mockLogEvent).toHaveBeenCalledWith('profile_edit_clicked'); }); }); it('should handle empty user state gracefully', () => { renderWithTheme(<UserProfile user={null} isLoading={false} />); expect(screen.getByText('No user data available')).toBeTruthy(); expect(screen.getByText('Please try again later')).toBeTruthy(); }); }); describe('Edge cases', () => { it('should truncate long names appropriately', () => { const user = getMockUser({ name: 'Christopher Alexander Montgomery Johnson III' }); renderWithTheme(<UserProfile user={user} />); expect(screen.getByText('Christopher Alexander...')).toBeTruthy(); }); }); });
Target Audience
React Native developers writing Jest tests, especially those transitioning to TDD or looking to improve test maintainability
Low security risk, safe to use
Testing Patterns and Utilities
Testing Philosophy
Test-Driven Development (TDD):
- Write failing test FIRST
- Implement minimal code to pass
- Refactor after green
- Never write production code without a failing test
Behavior-Driven Testing:
- Test behavior, not implementation
- Focus on public APIs and business requirements
- Avoid testing implementation details
- Use descriptive test names that describe behavior
Factory Pattern:
- Create
getMockX(overrides?: Partial<X>)functions - Provide sensible defaults
- Allow overriding specific properties
- Keep tests DRY and maintainable
Test Utilities
Custom Render Function
Create a custom render that wraps components with required providers:
// 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:
import { renderWithTheme } from 'utils/testUtils';
import { screen } from '@testing-library/react-native';
it('should render component', () => {
renderWithTheme(<MyComponent />);
expect(screen.getByText('Hello')).toBeTruthy();
});
Factory Pattern
Component Props Factory
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();
});
Data Factory
interface 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();
});
Mocking Patterns
Mocking Modules
// 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;
Mocking 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,
});
Test Structure
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', () => {});
});
});
Query Patterns
// 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();
});
User Interaction Patterns
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'), 'user@example.com');
fireEvent.changeText(screen.getByLabelText('Password'), 'password123');
fireEvent.press(screen.getByTestId('login-button'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
});
});
Anti-Patterns to Avoid
Testing Mock Behavior Instead of Real Behavior
// Bad - testing the mock
expect(mockFetchData).toHaveBeenCalled();
// Good - testing actual behavior
expect(screen.getByText('John Doe')).toBeTruthy();
Not Using Factories
// 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' });
Best Practices
- Always use factory functions for props and data
- Test behavior, not implementation
- Use descriptive test names
- Organize with describe blocks
- Clear mocks between tests
- Keep tests focused - one behavior per test
Running Tests
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run specific file
npm test ComponentName.test.tsx
Integration with Other Skills
- react-ui-patterns: Test all UI states (loading, error, empty, success)
- systematic-debugging: Write test that reproduces bug before fixing
Source: https://github.com/ChrisWiles/claude-code-showcase#.claude~skills~testing-patterns
Content curated from original sources, copyright belongs to authors
User Rating
USER RATING
WORKS WITH