---
description: Mobile Testing
alwaysApply: false
---

# Mobile Testing

Testing strategies for mobile applications.

## Testing Pyramid

Unit (many, fast) → Component → Integration → E2E/device (few, slow via Detox).

## Unit Tests

```ts
describe('formatCurrency', () => {
  it('formats USD correctly', () => {
    expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
  });
  it('handles zero', () => {
    expect(formatCurrency(0, 'USD')).toBe('$0.00');
  });
});
```

## Component Tests

```tsx
it('calls onPress when pressed', () => {
  const onPress = jest.fn();
  const { getByText } = render(<Button label="Submit" onPress={onPress} />);
  fireEvent.press(getByText('Submit'));
  expect(onPress).toHaveBeenCalledTimes(1);
});
```

- Test loading, success, and error states with `waitFor`
- Mock API calls at the module level (`jest.mock('../services/api')`)

## Integration Tests

```tsx
const { getByText, getByPlaceholderText } = renderWithNavigation(<AuthNavigator />);
fireEvent.changeText(getByPlaceholderText('Email'), 'test@example.com');
fireEvent.changeText(getByPlaceholderText('Password'), 'password123');
fireEvent.press(getByText('Log In'));
await waitFor(() => expect(getByText('Welcome')).toBeTruthy());
```

- Wrap components in `NavigationContainer` for nav tests

## E2E Tests (Detox)

```ts
it('should login successfully', async () => {
  await element(by.id('email-input')).typeText('test@example.com');
  await element(by.id('password-input')).typeText('password123');
  await element(by.id('login-button')).tap();
  await expect(element(by.id('home-screen'))).toBeVisible();
});
```

- Use `testID` props for reliable selectors
- Use `waitFor(...).withTimeout()` instead of fixed delays

## Mocking

- Mock native modules in `jest.setup.js` (AsyncStorage, NetInfo, DeviceInfo)
- Mock navigation: `useNavigation` → `{ navigate: jest.fn(), goBack: jest.fn() }`
- Co-locate tests with source: `Button/Button.tsx` + `Button/Button.test.tsx`

## Anti-Patterns

- **Testing implementation** — test behavior (what user sees), not internal state
- **No async handling** — always use `waitFor` for async renders; never rely on timing
- **Skipping platform differences** — test on both iOS and Android in CI for E2E
