How to mock an API response without touching the backend

Four ways to hand an app your own JSON: Chrome Local Overrides, MSW, a client interceptor and a proxy, and when each one fits.

When you need it

You want to see how the app handles a response the backend will not give you: an empty list, a 500, two hundred items instead of three, a field that does not exist yet. The backend is off limits: it is someone else's, or it is being worked on, or it is production. There are four ways to do this, and they differ in where the swap happens, so each one reaches different traffic.

Chrome DevTools: Local Overrides

Fastest way for the web. Nothing to install.

  1. DevTools → Network → right-click the request → Override content.
  2. Pick a folder on disk and allow access to it.
  3. The response opens in an editor. Edit it and save with Ctrl/Cmd+S.
  4. Reload. That request now comes from your file.

Two limits. You can override the body and the response headers, but not the status code, so you cannot turn a 200 into a 500 this way. And it works only in this browser, with DevTools open: close DevTools and the real response is back.

MSW: mocking inside the application

msw registers a Service Worker in the page. The app sends its requests as usual. Your code answers them.

First copy the worker file into your public folder:

npx msw init ./public --save
// mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/orders', () =>
    HttpResponse.json({ items: [] }),          // an empty list
  ),
  http.post('/api/orders', () =>
    new HttpResponse(null, { status: 500 }),   // a server error
  ),
];
// mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
// main.jsx — development only
if (import.meta.env.DEV) {
  const { worker } = await import('./mocks/browser');
  await worker.start();
}
// render the app after this point

Wait for start() before the app renders. Requests sent earlier go to the real backend.

Best option when the mock has to be repeatable: it is code, it lives in the repository, the whole team gets it, and tests reuse the same handlers through setupServer from msw/node. The limit: it covers your own web app only, not somebody else's and not a native client.

An interceptor in the mobile client

If the app is yours, mock it where the networking is.

// Android, OkHttp
val mockInterceptor = Interceptor { chain ->
    val request = chain.request()
    if (BuildConfig.DEBUG && request.url.encodedPath == "/api/orders") {
        Response.Builder()
            .request(request)
            .protocol(Protocol.HTTP_1_1)
            .code(500)
            .message("Mocked")
            .body("""{"error":"boom"}""".toResponseBody("application/json".toMediaType()))
            .build()
    } else {
        chain.proceed(request)
    }
}

val client = OkHttpClient.Builder()
    .addInterceptor(mockInterceptor)
    .build()

Keep the BuildConfig.DEBUG check. Without it the mock ships to users.

The cost: you need the source, and every change needs a rebuild.

A proxy: mocking outside the application

A debugging proxy swaps the response on the wire, not inside the app. So it does not care whose app it is or what it is written in: a web page, a native mobile client, a CLI tool, somebody else's binary with no source. One rule says it all: for this URL, send this response, meaning status, headers and body, with a delay if you want one. A request that matches never reaches the network.

It is the only one of the four that works when:

  • the app is not yours and cannot be rebuilt;
  • the traffic comes from a phone rather than a browser;
  • you need to show a colleague or a tester without making them set up an environment.

One limit matters. An app that pins its certificate rejects the proxy's certificate, so the connection fails and you see nothing in that traffic, and change nothing either.

Which to use

What you need What to use
A one-off look in your own browser Local Overrides
A scenario the whole team and the tests share MSW
Mocking inside your own mobile client an interceptor in code
Somebody else's app, or a phone, without a rebuild a proxy

See it on your own traffic

In Solpuga a Map Local rule holds the whole response: status, headers and body. A request it answered is badged Rule in the list, so you can tell the rule fired. The usual mocking failure is a rule that never matched: the path is off, a query string was left in the pattern, or the app is showing cached data. Full reference: Map Local.

View in Solpuga

Related