Testing Expo Applications
1. Setting Up Jest
Expo provides built-in support for Jest, which is the default testing framework used in Expo projects. To set up Jest in your Expo project, follow these steps:
| Configuration File | Path | Description |
|---|---|---|
| jest.config.js | jest.config.js |
Configuration file for Jest settings |
Example: Basic Jest Configuration
module.exports = {
preset: 'jest-expo',
testEnvironment: 'jest-environment-jsdom',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
testPathIgnorePatterns: ['/node_modules/', '/e2e/'],
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|react-native-svg|@react-native-async-storage/async-storage))',
],
};
preset: 'jest-expo' configuration is essential for Expo projects to ensure compatibility with Expo's environment and features.
e2e directories from test runs to avoid conflicts.
2. Writing Component Tests
Example: Testing a Simple Component
import React from 'react';
import { render, screen } from '@testing-library/react-native';
import { Text } from 'react-native';
const Greeting = ({ name }) => <Text>Hello, {name}!</Text>;
test('renders greeting with name', () => {
render(<Greeting name="Expo" />);
expect(screen.getByText('Hello, Expo!')).toBeTruthy();
});
| Test Function | Description | Notes |
|---|---|---|
| test() | Defines a test case | Uses Jest under the hood |
| render() | Renders a component in a test environment | Part of @testing-library/react-native |
| screen.getByText() | Finds a text element by its content | Useful for verifying rendered output |
jest-expo is added to your jest.config.js for proper Expo support.
Example: Testing with Mocks
import React from 'react';
import { render, screen } from '@testing-library/react-native';
// Mocking a third-party library
jest.mock('react-native-vector-icons', () => ({
Icon: 'Icon',
}));
const ComponentWithIcon = () => (
<View>
<Icon name="home" size={20} />
</View>
);
test('renders icon component', () => {
render(<ComponentWithIcon />);
expect(screen.getByText('Icon')).toBeTruthy();
});
3. Testing Custom Hooks
| Property | Description | Example |
|---|---|---|
| renderHook | Renders a custom hook and provides access to its state and result | renderHook(() => useCustomHook()) |
| result | Current result of the hook | result.current |
| rerender | Re-runs the hook with new props | rerender({ prop: newValue }) |
Example: Testing Custom Hook
import { renderHook } from '@testing-library/react-native';\nimport useCounter from './useCounter';\n\nit('should increment counter', () => {\n const { result } = renderHook(() => useCounter(0));\n expect(result.current.count).toBe(0);\n\n act(() => {\n result.current.increment();\n });\n expect(result.current.count).toBe(1);\n});
act when updating state in tests to ensure proper async handling.
Testing Custom Hooks Summary
- Use
renderHookto test custom hooks in isolation - Access hook state and result via
result.current - Use
rerenderto simulate prop changes
4. Mocking Dependencies
| Dependency | Mocked Value | Notes |
|---|---|---|
| jest | {} |
Used for unit testing components and functions |
| react-native-mock | {} |
Provides mock implementations for React Native APIs |
Example: Mocking a Network Request
import { jest } from '@jest/globals';\n\n// Mock the fetch API\nconst mockFetch = jest.fn();\n\n// Set up the mock response\nmockFetch.mockResolvedValueOnce({\n json: jest.fn().mockResolvedValue({ data: 'mocked data' }),\n});\n\n// Replace the global fetch with our mock\nglobal.fetch = mockFetch;
5. Testing Navigation
| Feature | Description | Notes |
|---|---|---|
| navigation.navigate | navigate('ScreenName', params) |
Programmatically navigate to a screen |
| navigation.goBack | goBack() |
Go back to the previous screen |
| navigation.push | push('ScreenName', params) |
Add a new screen to the stack |
| navigation.replace | replace('ScreenName', params) |
Replace current screen with a new one |
Example: Testing Navigation with React Navigation
import { render } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
const TestScreen = () => <View><Text>Test Screen</Text></View>;
const TestApp = () => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Test" component={TestScreen} />
</Stack.Navigator>
</NavigationContainer>
);
test('renders TestScreen', () => {
const { getByText } = render(<TestApp />);
expect(getByText('Test Screen')).toBeTruthy();
});
NavigationContainer in tests to ensure navigation context is available.
6. Using Detox
Detox is a gray-box end-to-end testing framework for React Native and Expo applications. It allows you to write tests that run against a real device or simulator, providing a more realistic testing environment than unit tests alone.
| Feature | Description | Notes |
|---|---|---|
| Test Runner | Uses Mocha as the test runner | Configurable via detox.config.js |
| Device Support | Supports iOS and Android simulators/emulators | Requires Xcode and Android Studio for local testing |
| Test Syntax | Uses Chai for assertions and Mocha for test structure | Can be extended with custom matchers |
| Native UI Interaction | Can interact with native components | Uses device and element APIs |
Example: Basic Detox Test
describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});
it('should show the welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});
expo-dev-client to enable debugging and testing capabilities.
expo-dev-client.
Key Points
- Detox is a gray-box end-to-end testing framework for React Native and Expo apps
- It supports iOS and Android simulators/emulators
- Uses Mocha as the test runner and Chai for assertions
- Requires
expo-dev-clientfor testing in development builds - Detox tests cannot run in Expo Go
7. Configuring Detox
Detox is a gray-box end-to-end testing framework for React Native and Expo applications. It allows you to write tests that simulate real user interactions with your app.
| Configuration File | Path | Description |
|---|---|---|
| detox | detox.config.js |
Main configuration file for Detox setup |
Example: Basic Detox Configuration
{
"testEnvironment": "detox",
"testTimeout": 120000,
"suite": "e2e",
"configurations": {
"ios.sim.debug": {
"binaryPath": "build/ios/Debug-iphonesimulator/MyApp.app",
"build": "detox build --configuration ios.sim.debug",
"type": "ios.simulator",
"name": "iPhone 14"
},
"android.emu.debug": {
"binaryPath": "build/android/app-debug.apk",
"build": "detox build --configuration android.emu.debug",
"type": "android.emulator",
"name": "Pixel_4_API_30"
}
}
}
Key Configuration Options
- binaryPath: Path to the built app binary (iOS or Android)
- build: Command to build the app for testing
- type: Platform type (ios.simulator, android.emulator)
- name: Device or emulator name to run tests on
8. Writing E2E Tests
End-to-end (E2E) tests in Expo validate the application's behavior from user interaction to backend responses. These tests are crucial for ensuring that components work together as expected in a real-world environment.
| Test Framework | Description | Usage |
|---|---|---|
| Detox | Popular E2E testing framework for React Native and Expo apps | detox init to set up project |
| Cypress | Browser-based E2E testing framework, supports Expo web builds | cypress open to run tests in browser |
Example: Basic Detox Test
describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});
it('should show welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});
9. Running Tests
Example: Running Jest tests in Expo
// Run all tests
npm test
// Run tests in watch mode
npm test -- --watch
// Run specific test file
npm test -- src/__tests__/App.test.js
| Command | Description | Notes |
|---|---|---|
| npm test | Run all tests using Jest | Default test runner for Expo projects |
| npm test -- --watch | Run tests in watch mode | Automatically re-run tests on file changes |
| npm test -- <file> | Run specific test file | Useful for debugging individual tests |
jest and jest-expo as dev dependencies.
10. Generating Coverage Reports
Example: Setting up Jest with coverage
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!src/**/*.test.js',
],
coverageDirectory: 'coverage',
coverageReporters: ['html', 'text'],
};
| Option | Values | Description |
|---|---|---|
| collectCoverage | true |
Enables coverage collection |
| collectCoverageFrom | Array<string> |
Patterns to include in coverage |
| coverageDirectory | string |
Directory to write coverage reports to |
| coverageReporters | Array<string> |
Types of coverage reports to generate |
Steps to generate coverage reports
- Configure Jest with
collectCoverage - Set up
coverageDirectoryandcoverageReporters - Run tests with the coverage flag
11. Testing Async Operations
| Operation | Description | Example |
|---|---|---|
| async/await | Used to handle asynchronous operations in tests | |
| jest.useFakeTimers() | Mock timers to control time in async tests | |
| jest.runAllTimers() | Run all pending timers immediately | |
12. Snapshot Testing
| Feature | Description | Usage |
|---|---|---|
| jest | Jest is the testing framework used for snapshot testing in Expo. | npm install --save-dev jest |
| snapshot | Snapshot tests capture a serialized version of your component's output. | expect(component).toMatchSnapshot() |
| update snapshots | Use --updateSnapshot to update the snapshot file. |
npm test -- --updateSnapshot |
Example: Snapshot Test
import React from 'react';
import { render } from '@testing-library/react-native';
import { Text } from 'react-native';
it('renders correctly', () => {
const { toJSON } = render(<Text>Hello World</Text>);
expect(toJSON()).toMatchSnapshot();
});