Skip to main content

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:

npm install --save-dev jest @types/jest
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))',
  ],
};
Note: The preset: 'jest-expo' configuration is essential for Expo projects to ensure compatibility with Expo's environment and features.
Warning: Make sure to exclude 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
Note: Ensure 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});
Note: Use act when updating state in tests to ensure proper async handling.

Testing Custom Hooks Summary

  • Use renderHook to test custom hooks in isolation
  • Access hook state and result via result.current
  • Use rerender to 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;
Note: When mocking dependencies in Expo, ensure that the mocks are correctly configured to avoid runtime errors during tests.

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();
});
Note: Use 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: Setting up Detox in an Expo Project

npm install --save-dev detox

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();
  });
});
Note: Detox requires a specific setup for Expo projects, including the use of expo-dev-client to enable debugging and testing capabilities.
Warning: Detox tests are not supported in the Expo Go app. They must be run using a development build created with 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-client for 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"
    }
  }
}
Note: Ensure that your Expo project is set up to support Detox by installing the required dependencies and configuring the build process correctly.

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: Detox Setup

npm install --save-dev detox
detox init

Example: Basic Detox Test

describe('Example', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  it('should show welcome screen', async () => {
    await expect(element(by.id('welcome'))).toBeVisible();
  });
});
Note: Detox requires native builds and is best suited for iOS and Android environments.
Warning: E2E tests can be slow and flaky due to device/emulator delays. Ensure proper test isolation.

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
Note: Expo uses Jest as its default test runner. Make sure to install jest and jest-expo as dev dependencies.
Warning: Tests must be run in the Expo Go environment or a simulator/emulator, not in a web browser.

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
Note: Coverage reports require Jest configuration to be set up properly.

Example: Running coverage with Expo

expo test --coverage

Steps to generate coverage reports

  • Configure Jest with collectCoverage
  • Set up coverageDirectory and coverageReporters
  • Run tests with the coverage flag

11. Testing Async Operations

Operation Description Example
async/await Used to handle asynchronous operations in tests
test('fetches user data', async () => {
  const userData = await fetchUser(1);
  expect(userData.name).toBe('John');
});
jest.useFakeTimers() Mock timers to control time in async tests
jest.useFakeTimers();
const delayedValue = setTimeout(() => 'result', 1000);
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers() Run all pending timers immediately
jest.useFakeTimers();
const spy = jest.fn();
setTimeout(spy, 1000);
jest.runAllTimers();
expect(spy).toHaveBeenCalledTimes(1);
Note: When testing async operations, ensure all promises are properly awaited to avoid false positives in test results.

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();
});
Note: Snapshots should be updated when component changes are intentional.
Warning: Snapshot tests can be brittle if not managed carefully.

Was this article helpful?