Testing Guide
How to test React Native apps that use react-native-config-ultimate.
Key insight: Config values are injected at build time and read from native code. In tests, you need to mock the module so tests don’t depend on generated files.
Compatibility: Works with both Old and New Architecture (TurboModules). Tested with React Native 0.73+ and React 18/19.
Testing Pyramid
┌─────────────────┐
│ E2E (Detox / │ ← Real simulator, real native build
│ Maestro) │ Catches native-layer integration issues
├─────────────────┤
│ Integration │ ← Component tests with react-test-renderer
│ (Jest + RTL) │ Catches JS-layer rendering issues
├─────────────────┤
│ Unit (Jest) │ ← Pure logic, fully mocked
└─────────────────┘
1. Unit Tests (Jest)
Run
# From the library package (100+ tests)
cd packages/react-native-config-ultimate
npm test
# From your own app
cd your-app
npx jest
Mocking the config in your tests
react-native-config-ultimate reads values that are baked in at native build
time. In Jest, you must mock the module so tests don’t depend on generated
files or native code.
Option A: Inline mock per test file
Best for: Tests that need different config values.
// src/components/ApiLabel.test.tsx
jest.mock('react-native-config-ultimate', () => ({
API_URL: 'https://mock-api.test',
APP_ENV: 'test',
DEBUG_MODE: true,
TIMEOUT_MS: 5000,
}));
import Config from 'react-native-config-ultimate';
// Config.API_URL === 'https://mock-api.test'
Option B: Global mock via moduleNameMapper
Best for: Consistent mock across all tests.
// jest.config.js
module.exports = {
preset: 'react-native',
moduleNameMapper: {
'react-native-config-ultimate': '<rootDir>/__mocks__/config.ts',
},
};
// __mocks__/config.ts
const Config = {
API_URL: 'https://mock-api.test',
APP_ENV: 'test',
DEBUG_MODE: false,
TIMEOUT_MS: 3000,
};
export default Config;
Option C: Jest manual mock (auto-discovered)
Best for: Zero-config mocking.
your-app/
└── __mocks__/
└── react-native-config-ultimate.ts ← auto-discovered by Jest
// __mocks__/react-native-config-ultimate.ts
const Config = {
API_URL: 'https://mock-api.test',
APP_ENV: 'test',
DEBUG_MODE: false,
};
export default Config;
Option D: Type-safe mock with TypeScript
Best for: Full TypeScript support in tests.
// __mocks__/react-native-config-ultimate.ts
import type { ConfigVariables } from 'react-native-config-ultimate';
const Config: ConfigVariables = {
API_URL: 'https://mock-api.test',
APP_ENV: 'test',
DEBUG_MODE: false,
TIMEOUT_MS: 3000,
};
export default Config;
Example test
// src/components/ApiLabel.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react-native';
import ApiLabel from './ApiLabel';
jest.mock('react-native-config-ultimate', () => ({
API_URL: 'https://mock-api.test',
}));
it('renders the API URL from config', () => {
render(<ApiLabel />);
expect(screen.getByText('https://mock-api.test')).toBeTruthy();
});
2. E2E with Maestro (recommended for beginners)
Maestro is the simplest e2e framework for React Native — no native build configuration needed beyond a running app in a simulator.
Install Maestro
curl -Ls "https://get.maestro.mobile.dev" | bash
Prepare the app
cd packages/example
# Generate native config files
npx rncu .env
# Launch the app in a simulator
npx react-native run-ios # iOS
npx react-native run-android # Android
Run Maestro flows
# Single flow
maestro test .maestro/01_config_display.yaml
# All flows
maestro test .maestro/
# With env parameter (for 02_env_switching.yaml)
maestro test .maestro/02_env_switching.yaml --env ENV=staging
Flow anatomy
# .maestro/01_config_display.yaml
appId: com.example # your bundle ID
---
- launchApp:
clearState: true
- assertVisible:
id: "section-title-config" # matches testID="section-title-config" in App.tsx
- assertVisible:
text: "HELLO" # from .env: HELLO=world
Available testIDs in the example app
testID |
What it is |
|---|---|
section-config |
Config section container |
section-title-config |
“Config” title text |
section-body-config |
Config section body |
config-values |
The raw JSON.stringify(config) text |
3. E2E with Detox (full native integration)
Detox runs against a real native build. It catches issues that Maestro can’t: native module loading failures, TurboModule initialization errors, etc.
Install Detox
cd packages/example
npm install --save-dev detox @types/detox
# iOS only — install xcpretty
gem install xcpretty
Prepare the app
cd packages/example
# 1. Generate native config
npx rncu .env
# 2. Install iOS pods
cd ios && pod install && cd ..
# 3. Build for Detox
npm run test:e2e:build:ios # iOS simulator
npm run test:e2e:build:android # Android emulator
Run Detox tests
# iOS
npm run test:e2e:ios
# Android
npm run test:e2e:android
# With options
npx detox test --configuration ios.sim.debug --reuse # reuse running sim
npx detox test --configuration ios.sim.debug --take-screenshots all
npx detox test --configuration ios.sim.debug --record-videos all
Test anatomy
// e2e/config.test.ts
describe('Config Display', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
it('should render config values from .env', async () => {
// Uses testID set in App.tsx
await expect(element(by.id('config-values'))).toBeVisible();
// Assert a specific key is shown (HELLO=world from .env)
await expect(
element(by.id('config-values').and(by.text(/HELLO/))),
).toExist();
});
});
Testing environment switching with Detox
Since env values are baked in at build time, test different envs by building separately:
# Build + test with staging env
npx rncu .env.staging
npm run test:e2e:build:ios
npx detox test --configuration ios.sim.debug
# Build + test with production env
npx rncu .env.production
npm run test:e2e:build:ios
npx detox test --configuration ios.sim.debug
Configurations in .detoxrc.js
| Configuration | Platform | Mode |
|---|---|---|
ios.sim.debug |
iOS Simulator | Debug |
ios.sim.release |
iOS Simulator | Release |
android.emu.debug |
Android Emulator | Debug |
android.emu.release |
Android Emulator | Release |
4. CI/CD
GitHub Actions — Unit tests (already configured)
# .github/workflows/test.yml (already in repo)
- run: npm test
GitHub Actions — Maestro (add to workflow)
- name: Install Maestro
run: curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Run Maestro flows
run: |
npx rncu .env
npx react-native run-ios --simulator "iPhone 15"
maestro test .maestro/
GitHub Actions — Detox iOS (macOS runner required)
jobs:
e2e-ios:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: gem install xcpretty
- run: npm install
- run: cd packages/example && npx rncu .env
- run: cd packages/example/ios && pod install
- run: cd packages/example && npm run test:e2e:build:ios
- run: cd packages/example && npm run test:e2e:ios
Quick reference
| What | Command | Where |
|---|---|---|
| Unit tests (library) | npm test |
packages/react-native-config-ultimate/ |
| Unit tests (example app) | npm test |
packages/example/ |
| Maestro e2e | maestro test .maestro/ |
packages/example/ |
| Detox build iOS | npm run test:e2e:build:ios |
packages/example/ |
| Detox run iOS | npm run test:e2e:ios |
packages/example/ |
| Detox build Android | npm run test:e2e:build:android |
packages/example/ |
| Detox run Android | npm run test:e2e:android |
packages/example/ |
Related
- Quickstart — Installation and setup
- API Reference — Full API documentation
- Troubleshooting — Common issues and solutions