Documentation Index Fetch the complete documentation index at: https://mintlify.com/Expensify/App/llms.txt
Use this file to discover all available pages before exploring further.
Testing Philosophy
New Expensify follows a comprehensive testing strategy:
Unit Tests : Test individual components and functions
Integration Tests : Test feature workflows
Performance Tests : Catch performance regressions
Type Safety : TypeScript for compile-time checks
Running Tests
All Tests
# Run all tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run specific test file
npm test -- MyComponent.test.tsx
# Run tests matching pattern
npm test -- --testNamePattern= "renders correctly"
Test Coverage
# Generate coverage report
npm test -- --coverage
# View in browser
open coverage/lcov-report/index.html
Writing Unit Tests
Component Tests
import { render , fireEvent , screen } from '@testing-library/react-native' ;
import Onyx from 'react-native-onyx' ;
import Button from '@components/Button' ;
import ONYXKEYS from '@src/ONYXKEYS' ;
describe ( 'Button' , () => {
beforeAll (() => {
Onyx . init ({ keys: ONYXKEYS });
});
afterEach (() => {
Onyx . clear ();
});
it ( 'renders with text' , () => {
render (< Button text = "Click Me" />);
expect ( screen . getByText ( 'Click Me' )). toBeTruthy ();
});
it ( 'calls onPress when pressed' , () => {
const onPress = jest . fn ();
render (< Button text = "Click Me" onPress ={ onPress } />);
fireEvent . press ( screen . getByText ( 'Click Me' ));
expect ( onPress ). toHaveBeenCalledTimes ( 1 );
});
it ( 'is disabled when disabled prop is true' , () => {
const onPress = jest . fn ();
render (< Button text = "Click Me" isDisabled onPress ={ onPress } />);
fireEvent . press ( screen . getByText ( 'Click Me' ));
expect ( onPress ). not . toHaveBeenCalled ();
});
});
Testing with Onyx
import waitForBatchedUpdates from '@libs/waitForBatchedUpdates' ;
import * as TestHelper from '@tests/unit/TestHelper' ;
describe ( 'ReportScreen' , () => {
beforeAll (() => {
Onyx . init ({ keys: ONYXKEYS });
});
beforeEach (() => {
// Set up test data
return Onyx . merge ( ONYXKEYS . SESSION , {
authToken: 'test-token' ,
accountID: 1 ,
email: 'test@example.com' ,
});
});
afterEach (() => {
Onyx . clear ();
});
it ( 'displays report name' , async () => {
const reportID = '123' ;
await Onyx . merge ( ` ${ ONYXKEYS . COLLECTION . REPORT }${ reportID } ` , {
reportID ,
reportName: 'Test Report' ,
});
render (< ReportScreen reportID ={ reportID } />);
await waitForBatchedUpdates ();
expect ( screen . getByText ( 'Test Report' )). toBeTruthy ();
});
});
Testing Hooks
import { renderHook } from '@testing-library/react-hooks' ;
import { useReportActions } from '@hooks/useReportActions' ;
describe ( 'useReportActions' , () => {
it ( 'returns sorted actions' , async () => {
const reportID = '123' ;
await Onyx . merge ( ` ${ ONYXKEYS . COLLECTION . REPORT_ACTIONS }${ reportID } ` , {
'1' : { created: '2024-01-01' , message: 'First' },
'2' : { created: '2024-01-02' , message: 'Second' },
});
const { result } = renderHook (() => useReportActions ( reportID ));
await waitForBatchedUpdates ();
expect ( result . current ). toHaveLength ( 2 );
expect ( result . current [ 0 ]. message ). toBe ( 'First' );
});
});
Mocking API Calls
import * as API from '@libs/API' ;
jest . mock ( '@libs/API' );
describe ( 'createWorkspace' , () => {
beforeEach (() => {
jest . clearAllMocks ();
});
it ( 'calls API with correct parameters' , () => {
const mockWrite = jest . spyOn ( API , 'write' );
createWorkspace ( 'Test Workspace' );
expect ( mockWrite ). toHaveBeenCalledWith (
'CreateWorkspace' ,
{ policyName: 'Test Workspace' },
expect . objectContaining ({
optimisticData: expect . any ( Array ),
}),
);
});
});
Testing Utilities
Custom Render with Providers
import { render } from '@testing-library/react-native' ;
import { LocaleContextProvider } from '@components/LocaleContextProvider' ;
import ThemeProvider from '@components/ThemeProvider' ;
function renderWithProviders ( component : React . ReactElement ) {
return render (
< ThemeProvider >
< LocaleContextProvider >
{ component }
</ LocaleContextProvider >
</ ThemeProvider >
);
}
// Usage
test ( 'renders with providers' , () => {
renderWithProviders (< MyComponent />);
});
Waiting for Updates
import waitForBatchedUpdates from '@libs/waitForBatchedUpdates' ;
import { waitFor } from '@testing-library/react-native' ;
// Wait for Onyx updates
await waitForBatchedUpdates ();
// Wait for specific condition
await waitFor (() => {
expect ( screen . getByText ( 'Loaded' )). toBeTruthy ();
});
New Expensify uses Reassure for performance testing.
import { measurePerformance } from 'reassure' ;
import MyComponent from '../MyComponent' ;
test ( 'MyComponent renders efficiently' , async () => {
await measurePerformance (< MyComponent data ={ mockData } />);
});
# Run performance tests
npm run test:perf
# Compare with baseline
npm run test:perf -- --compare
See REASSURE_PERFORMANCE_TEST.md for details.
Testing Best Practices
1. Test Behavior, Not Implementation
// ✅ Good: Test what user sees
test ( 'displays error message' , () => {
render (< LoginForm />);
fireEvent . changeText ( screen . getByLabelText ( 'Email' ), 'invalid' );
fireEvent . press ( screen . getByText ( 'Submit' ));
expect ( screen . getByText ( 'Invalid email' )). toBeTruthy ();
});
// ❌ Bad: Test internal state
test ( 'sets error state' , () => {
const component = render (< LoginForm />);
expect ( component . instance (). state . error ). toBe ( true );
});
2. Keep Tests Independent
// ✅ Good: Each test sets up its own data
describe ( 'ReportList' , () => {
it ( 'shows empty state' , async () => {
await Onyx . merge ( ONYXKEYS . COLLECTION . REPORT , {});
render (< ReportList />);
expect ( screen . getByText ( 'No reports' )). toBeTruthy ();
});
it ( 'shows reports' , async () => {
await Onyx . merge ( ONYXKEYS . COLLECTION . REPORT , {
'1' : { reportID: '1' , reportName: 'Report 1' },
});
render (< ReportList />);
expect ( screen . getByText ( 'Report 1' )). toBeTruthy ();
});
});
3. Use Descriptive Test Names
// ✅ Good: Clear what is being tested
it ( 'displays error when email is invalid' , () => {});
it ( 'disables submit button when form is submitting' , () => {});
it ( 'navigates to home screen after successful login' , () => {});
// ❌ Bad: Vague test names
it ( 'works correctly' , () => {});
it ( 'test 1' , () => {});
4. Clean Up After Tests
describe ( 'MyComponent' , () => {
afterEach (() => {
jest . clearAllMocks ();
Onyx . clear ();
});
// Tests...
});
Testing Checklist
Before submitting a PR, ensure:
Common Testing Patterns
Testing Async Operations
it ( 'loads data asynchronously' , async () => {
render (< DataComponent />);
expect ( screen . getByText ( 'Loading...' )). toBeTruthy ();
await waitFor (() => {
expect ( screen . getByText ( 'Data loaded' )). toBeTruthy ();
});
});
Testing Navigation
import Navigation from '@libs/Navigation/Navigation' ;
jest . mock ( '@libs/Navigation/Navigation' );
it ( 'navigates to settings on button press' , () => {
render (< MyComponent />);
fireEvent . press ( screen . getByText ( 'Settings' ));
expect ( Navigation . navigate ). toHaveBeenCalledWith ( ROUTES . SETTINGS );
});
it ( 'submits form with valid data' , () => {
const onSubmit = jest . fn ();
render (< MyForm onSubmit ={ onSubmit } />);
fireEvent . changeText ( screen . getByLabelText ( 'Name' ), 'John' );
fireEvent . changeText ( screen . getByLabelText ( 'Email' ), 'john@example.com' );
fireEvent . press ( screen . getByText ( 'Submit' ));
expect ( onSubmit ). toHaveBeenCalledWith ({
name: 'John' ,
email: 'john@example.com' ,
});
});
Debugging Tests
View Component Output
import { screen , debug } from '@testing-library/react-native' ;
test ( 'debugging test' , () => {
render (< MyComponent />);
// Print component tree
debug ();
// Print specific element
debug ( screen . getByText ( 'Hello' ));
});
Run Single Test
# Run specific test file
npm test -- MyComponent.test.tsx
# Run specific test case
npm test -- --testNamePattern= "renders correctly"
# Run in watch mode for debugging
npm test -- --watch MyComponent.test.tsx
Next Steps
Pull Requests Submit PRs with tests
Coding Standards Follow code quality standards
Architecture Understand what to test
Performance Tests Performance testing guide