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.
- DevTools → Network → right-click the request → Override content.
- Pick a folder on disk and allow access to it.
- The response opens in an editor. Edit it and save with
Ctrl/Cmd+S. - 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.
Related
- How to see the traffic from an iPhone or an Android phonePhone and computer on one Wi-Fi, a proxy in the network settings, a root certificate with full trust. Four steps, and the six things that break them.
- SSE arrives in one chunk: nginx bufferingServer-Sent Events land all at once instead of streaming. Three layers hold them: proxy_buffering, gzip and the app itself. How to test each.