Using network mocks in Detox tests lets a mobile end-to-end case verify UI behavior without depending on a production API, shared staging data, or an unpredictable third-party service. The test starts a local mock server, launches the app with that server URL, and asserts that the screen renders the mocked response.
Detox passes runtime values into the app through launch arguments, while the app code remains responsible for reading those values and choosing the API base URL. This pattern keeps the mock boundary explicit: Detox controls the test setup, and the app under test performs the same network request path it uses in normal runtime code.
Keep the mock server private to the test process and use neutral test data. A passing spec should prove both sides of the handoff by showing the mock response in the app UI, not only that the server started or that device.launchApp() accepted a launch argument.
Related: How to use launch arguments in Detox tests
Related: How to reset test data before Detox tests
Related: How to run Detox tests
Steps to use network mocks in Detox tests:
- Add an app-side API base URL helper that reads a test launch argument.
- src/config/apiBaseUrl.js
import { LaunchArguments } from 'react-native-launch-arguments'; const productionApiBaseUrl = 'https://api.example.com'; export function getApiBaseUrl() { const launchArgs = LaunchArguments.value() || {}; if (launchArgs.e2eApiBaseUrl) { return launchArgs.e2eApiBaseUrl; } return productionApiBaseUrl; }
Guard test-only overrides so release builds cannot be pointed at arbitrary API hosts by accident. The app should use the override only for trusted E2E or debug builds.
- Create a local mock API helper for the Detox suite.
- e2e/helpers/mockApiServer.js
const http = require('http'); function createMockApiServer() { const routes = new Map(); const server = http.createServer((req, res) => { const url = new URL(req.url, 'http://localhost'); const route = routes.get(`${req.method} ${url.pathname}`); if (!route) { res.writeHead(404, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'missing mock route' })); return; } res.writeHead(route.status || 200, { 'content-type': 'application/json' }); res.end(JSON.stringify(route.body)); }); return new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', () => { server.off('error', reject); const { port } = server.address(); resolve({ port, baseUrl: `http://localhost:${port}`, route(method, path, response) { routes.set(`${method} ${path}`, response); }, reset() { routes.clear(); }, close() { return new Promise((resolveClose, rejectClose) => { server.close((error) => error ? rejectClose(error) : resolveClose()); }); }, }); }); }); } module.exports = { createMockApiServer };
The helper binds to a random local port so parallel workers do not need to share one fixed mock server. Add request-body parsing only for endpoints that need it.
- Wire the mock server into a focused Detox spec.
- e2e/profile-network.e2e.js
const { createMockApiServer } = require('./helpers/mockApiServer'); describe('profile network mock', () => { let api; beforeAll(async () => { api = await createMockApiServer(); if (device.getPlatform() === 'android') { await device.reverseTcpPort(api.port); } }); afterAll(async () => { if (device.getPlatform() === 'android') { await device.unreverseTcpPort(api.port); } await api.close(); }); beforeEach(async () => { api.reset(); api.route('GET', '/profile', { status: 200, body: { name: 'E2E Shopper', plan: 'Mock Plus', }, }); await device.launchApp({ newInstance: true, launchArgs: { e2e: 'true', e2eApiBaseUrl: api.baseUrl, }, }); }); it('renders profile data from the mock API', async () => { await element(by.id('profile-tab')).tap(); await expect(element(by.id('profile-name'))).toHaveText('E2E Shopper'); await expect(element(by.id('profile-plan'))).toHaveText('Mock Plus'); }); });
device.reverseTcpPort() lets an Android app reach the host mock server through the same localhost URL used by an iOS Simulator run. Remove the reverse call for iOS-only projects.
- Run the focused network-mock spec with the project configuration.
$ npx detox test --configuration ios.sim.debug --reuse e2e/profile-network.e2e.js detox[run_tests] ios.sim.debug PASS e2e/profile-network.e2e.js profile network mock ✓ renders profile data from the mock API (4.9 s) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 totalUse the real configuration name from the project, such as ios.sim.debug or android.emu.debug. Keep Metro running for debug React Native builds before starting the spec.
Related: How to run Detox tests - Add a failure case for missing or unexpected network calls.
- e2e/profile-network.e2e.js
it('shows the profile error state when the mock API fails', async () => { api.reset(); api.route('GET', '/profile', { status: 503, body: { error: 'profile unavailable', }, }); await device.launchApp({ newInstance: true, launchArgs: { e2e: 'true', e2eApiBaseUrl: api.baseUrl, }, }); await element(by.id('profile-tab')).tap(); await expect(element(by.id('profile-error'))).toHaveText('Profile unavailable'); });
A 404 from the mock helper means the app called an endpoint that the test did not register. Treat that as a test setup mismatch or an app request-path change.
- Reset any server-side test data separately when the mocked flow still writes to a real backend.
Network mocks are best for controlled responses. Use a reset hook when the same Detox run also creates real records, sessions, or uploads.
Related: How to reset test data before Detox tests
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.