This document provides comprehensive information about the testing infrastructure for the Brigo React Native Expo Router app.
The project uses Jest with manual configuration (compatible with Expo SDK 54) and React Native Testing Library for testing. The testing infrastructure is designed to work seamlessly with Expo Router, TypeScript, Zustand, and Supabase.
# Run all tests
npm test
# Run tests in watch mode (recommended during development)
npm run test:watch
# Run tests with coverage report
npm run test:coverageTests are organized outside the app/ directory (as required by Expo Router) in the __tests__/ folder:
__tests__/
├── lib/
│ ├── errors/
│ │ ├── AppError.test.ts
│ │ ├── ErrorClassifier.test.ts
│ │ └── ErrorHandler.test.ts
│ ├── store/
│ │ └── slices/
│ │ ├── authSlice.test.ts
│ │ └── notebookSlice.test.ts
│ └── utils/
│ ├── utils.test.ts
│ └── time.test.ts
└── components/
└── ErrorModal.test.tsx
- Test files should be named
*.test.tsor*.test.tsx - Place test files in
__tests__directories or alongside source files (outsideapp/) - Use descriptive test names that explain what is being tested
The error handling system is fully tested:
- AppError: Tests for error creation, retry logic, user messages, and data serialization
- ErrorClassifier: Tests for error classification from various error types (Error instances, strings, objects, Supabase errors)
- ErrorHandler: Tests for error handling, retry logic, and error boundary creation
Example:
import { AppError } from '@/lib/errors/AppError';
import { ErrorType, ErrorSeverity } from '@/lib/errors/types';
describe('AppError', () => {
it('should create an AppError with all properties', () => {
const error = new AppError({
type: ErrorType.NETWORK,
message: 'Network request failed',
context: { operation: 'test', timestamp: new Date().toISOString() },
severity: ErrorSeverity.HIGH,
});
expect(error.type).toBe(ErrorType.NETWORK);
expect(error.severity).toBe(ErrorSeverity.HIGH);
});
});Store slices are tested by creating isolated store instances:
import { create } from 'zustand';
import { createAuthSlice } from '@/lib/store/slices/authSlice';
describe('authSlice', () => {
let useAuthStore: ReturnType<typeof create<AuthSlice>>;
beforeEach(() => {
useAuthStore = create<AuthSlice>()(createAuthSlice);
});
it('should set auth user', () => {
const mockUser = { id: 'user-123', email: 'test@example.com' };
useAuthStore.getState().setAuthUser(mockUser);
expect(useAuthStore.getState().authUser).toEqual(mockUser);
});
});Components are tested using React Native Testing Library:
import { render, fireEvent } from '@testing-library/react-native';
import { MyComponent } from '@/components/MyComponent';
describe('MyComponent', () => {
it('should render correctly', () => {
const { getByText } = render(<MyComponent title="Test" />);
expect(getByText('Test')).toBeTruthy();
});
it('should handle user interactions', () => {
const onPress = jest.fn();
const { getByText } = render(<MyComponent onPress={onPress} />);
fireEvent.press(getByText('Button'));
expect(onPress).toHaveBeenCalledTimes(1);
});
});Pure utility functions are straightforward to test:
import { formatTime } from '@/lib/utils/time';
describe('formatTime', () => {
it('should format seconds to MM:SS', () => {
expect(formatTime(0)).toBe('0:00');
expect(formatTime(125)).toBe('2:05');
});
});The Supabase client is automatically mocked in jest.setup.js. To customize mocks in specific tests:
import { supabase } from '@/lib/supabase';
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
single: jest.fn(() => Promise.resolve({ data: mockData, error: null })),
})),
},
}));Sentry is automatically mocked to prevent sending errors during tests:
import * as Sentry from '@/lib/sentry';
// Sentry functions are already mocked
// You can verify calls in tests:
expect(Sentry.captureAppError).toHaveBeenCalledWith(mockError);Common Expo modules are mocked in jest.setup.js:
expo-router- Router navigationexpo-secure-store- Secure storageexpo-constants- App constantsexpo-device- Device information@react-native-async-storage/async-storage- AsyncStorage
React Native modules are mocked where necessary:
react-native-safe-area-context- SafeAreaViewexpo-av- Audio playbackexpo-haptics- Haptic feedback
Main Jest configuration with:
jest-expopreset for Expo compatibility- Path alias mapping (
@/*→ root directory) - Test file patterns
- Coverage collection settings
Global test setup file that:
- Configures mocks for all external dependencies
- Sets up test environment variables
- Suppresses console warnings/errors during tests (optional)
Each test should be independent and not rely on state from previous tests:
beforeEach(() => {
// Reset state before each test
jest.clearAllMocks();
useStore.getState().reset();
});Test names should clearly describe what is being tested:
// Good
it('should retry network errors with exponential backoff')
// Bad
it('should work')Focus on what the code does, not how it does it:
// Good - tests behavior
it('should update notebook title when updateNotebook is called', () => {
// ...
expect(notebook.title).toBe('New Title');
});
// Bad - tests implementation details
it('should call setState with new title', () => {
// ...
expect(setState).toHaveBeenCalledWith({ title: 'New Title' });
});Always mock external services (Supabase, Sentry, native modules):
jest.mock('@/lib/supabase');
jest.mock('@/lib/sentry');Leverage TypeScript for type safety in tests:
import type { Notebook } from '@/lib/store/types';
const mockNotebook: Notebook = {
id: 'notebook-1',
title: 'Test',
// TypeScript will catch missing required fields
};Don't just test happy paths - test error handling:
it('should handle network errors gracefully', async () => {
mockService.fetchData.mockRejectedValue(new Error('Network error'));
await expect(loadData()).rejects.toThrow();
expect(errorHandler.handle).toHaveBeenCalled();
});Always await async operations in tests:
it('should load notebooks', async () => {
await store.getState().loadNotebooks();
expect(store.getState().notebooks).toHaveLength(1);
});Generate coverage reports with:
npm run test:coverageCoverage reports are generated in the coverage/ directory. The configuration collects coverage from:
lib/**/*.{ts,tsx}components/**/*.{ts,tsx}hooks/**/*.{ts,tsx}
Excludes:
- Type definition files (
*.d.ts) - Test files
- Node modules
If you encounter ReferenceError: You are trying to import a file outside of the scope of the test code:
- This is a known issue with jest-expo and Expo SDK 54
- Try running tests with:
NODE_OPTIONS=--experimental-vm-modules npm test - Alternatively, you may need to wait for jest-expo updates or use a workaround
- Some tests may work while others fail - this is expected with the current jest-expo version
If you see module resolution errors:
- Check that path aliases are configured in
jest.config.js - Verify
tsconfig.jsonhas matching path configuration - Ensure
jest.setup.jsis properly configured
If tests fail with Expo Router errors:
- Ensure tests are outside the
app/directory - Mock
expo-routerin your test file if needed - Check that
jest.setup.jsincludes Expo Router mocks
If async tests are flaky:
- Use
awaitfor all async operations - Use
jest.useFakeTimers()andjest.advanceTimersByTime()for timer-based code - Use
waitForfrom React Native Testing Library for component updates
If you see TypeScript errors in tests:
- Ensure
@types/jestis installed - Check that test files use
.test.tsor.test.tsxextension - Verify TypeScript configuration includes test files
- Create test file:
__tests__/lib/utils/myFunction.test.ts - Import the function
- Write tests for various inputs and edge cases
- Run tests:
npm test
- Create test file:
__tests__/lib/store/slices/mySlice.test.ts - Create isolated store instance
- Test all actions and state changes
- Mock any external dependencies (Supabase, services)
- Create test file:
__tests__/components/MyComponent.test.tsx - Mock required dependencies (theme, router, etc.)
- Test rendering and user interactions
- Test error states and edge cases
Tests should be run in CI/CD pipelines. Example GitHub Actions workflow:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm testFor questions or issues with testing:
- Check this documentation
- Review existing test files for examples
- Consult the Jest and React Native Testing Library documentation