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.

Steps to use network mocks in Detox tests:

  1. 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.

  2. 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.

  3. 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.

  4. 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 total

    Use 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

  5. Add the failure case inside the describe block of the same spec.
    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, which points to a test setup mismatch or an app request-path change.

  6. Run the spec again to confirm the success and failure cases both pass.
    $ npx detox test --configuration ios.sim.debug --reuse e2e/profile-network.e2e.js

    The Jest summary should now show Tests: 2 passed, 2 total. If only the new case fails, check that the app renders profile-error when the API returns a 503.
    Network mocks cover only the requests sent to the mock server, so a run that also creates real records, sessions, or uploads still needs a reset hook.
    Related: How to reset test data before Detox tests

Share this guide