Mock Server
The MockServer service is an in-process HTTP mock server that intercepts Angular's HTTP client requests and returns handler-defined responses. No separate server process required.
Overview
MockServer replaces Angular's HttpBackend with a custom implementation that matches incoming requests against registered handlers. Requests that match a handler are intercepted and answered locally. Requests that don't match are forwarded to the real network via FetchBackend.
- Zero dependencies
- Runs entirely inside the Angular application. No Node.js server, service worker, or proxy configuration needed.
- Route-aware path matching
- Uses Angular Router's
defaultUrlMatcherso path parameters like:idwork the same way as they do in your route definitions. - Async handler support
- Handlers can be synchronous or async. Use
await c.delay(ms)to simulate network latency inside an async handler, or rely on the globaldelayoption for a uniform delay on all synchronous responses. - Selective passthrough
- Unmatched URLs and unregistered HTTP methods fall through to the real network automatically.
Provider Setup
Add provideMockServer to your application configuration. Keep it behind a feature flag or environment check so it is only active during development:
import { provideMockServer } from '@/app/core/mock-server';
export const appConfig: ApplicationConfig = {
providers: [
provideMockServer({
baseURL: 'https://api.example.com',
delay: 400,
logging: {
bypass: false,
handler: true,
},
}),
],
};Configuration
| Option | Type | Description |
|---|---|---|
baseURL | string | The origin that the mock server handles (e.g. 'https://api.example.com'). Must start with http. Requests to other origins pass through. |
delay | number | Global delay in milliseconds applied to synchronous responses. Async handlers manage their own delay via c.delay(). |
logging.bypass | boolean | Log requests that were not matched and passed through to the network. |
logging.handler | boolean | Log requests that were handled by the mock server, including the response. |
Registering handlers
Inject MockServer and call the method that matches the HTTP verb you want to intercept. Handlers are typically registered in a service:
import { inject, Service } from '@angular/core';
import { MockServer } from '@/app/core/mock-server';
@Service()
export class ApiMockService {
private mockServer = inject(MockServer);
constructor() {
this.mockServer.get('/users', (c) => c.json([{ id: 1, name: 'Alice' }]));
this.mockServer.post('/users', (c) => c.json({ ...c.req.body, id: 2 }));
this.mockServer.delete('/users/:id', (c) => c.json(null, { status: 204 }));
}
}Handler context
Every handler receives a single context argument that exposes the incoming request and a set of response helpers:
| Property | Type | Description |
|---|---|---|
c.req.body | BodyType | null | The parsed request body. |
c.req.params | Record<string, string> | Path parameters extracted from the URL (e.g. { id: '4' } for a path /users/:id). Typed automatically from the path string when TypeScript can infer it. |
c.req.queryParams | HttpParams | Query string parameters. When the Angular HttpClient call supplies HttpParams, those take precedence over the raw URL query string. |
c.req.raw | HttpRequest<unknown> | The original Angular HTTP request object. |
c.json(body, options?) | Response | Returns a JSON response with Content-Type: application/json. Default status is 200. |
c.text(body, options?) | Response | Returns a plain-text response with Content-Type: text/plain. Default status is 200. |
c.blob(body, options?) | Response | Returns a binary response. The Content-Type is derived from the Blob.type, falling back to application/octet-stream when the type is empty. |
c.delay(ms) | Promise<void> | Awaitable helper that pauses the handler for the specified number of milliseconds. Use inside async handlers to simulate network latency. |
Usage examples
GET with path parameters
mockServer.get('/users/:id', (c) => {
const { id } = c.req.params; // typed as string
return c.json({ id, name: 'Alice' });
});POST with a request body
mockServer.post('/users', (c) => {
const body = c.req.body as { name: string };
return c.json({ id: 42, name: body.name }, { status: 201 });
});Query parameters
mockServer.get('/users', (c) => {
const page = c.req.queryParams.get('page') ?? '1';
const limit = c.req.queryParams.get('limit') ?? '10';
return c.json({ page, limit, items: [] });
});Custom status code and headers
mockServer.head('/users', (c) =>
c.json(null, {
headers: { 'Content-Length': '1024' },
})
);
mockServer.options('/users', (c) =>
c.json(null, {
headers: {
Allow: 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
},
})
);Error responses
Return any status code outside the 2xx range and the handler will emit an HttpErrorResponse to the caller, exactly like a real server error would.
mockServer.get('/users/:id', (c) => {
const { id } = c.req.params;
if (id === '0') {
return c.json({ message: 'Not found' }, { status: 404 });
}
return c.json({ id, name: 'Alice' });
});Simulated delay
mockServer.get('/users', async (c) => {
await c.delay(1200); // simulate a slow network
return c.json([{ id: 1, name: 'Alice' }]);
});Blob / file download
mockServer.get('/files/:name', (c) => {
const content = new Blob(['hello, world'], { type: 'text/plain' });
return c.blob(content);
});How it works
provideMockServerreplaces Angular'sHttpBackendwithMockServerBackend.- On every outgoing request,
MockServerBackendasksMockServerwhether a handler exists for that URL and method. - If a match is found, the handler runs and returns an
Observable<HttpEvent>that is indistinguishable from a real HTTP response as far as the Angular HTTP client is concerned. - If no match is found, the request is forwarded to the underlying
FetchBackendso real network calls continue to work normally.
Caveats
Intercepted requests never leave Angular, so they will not appear in the browser's Network tab. The mock server short-circuits the request entirely before it reaches the browser's fetch infrastructure. Use the built-in logging.handler option to inspect intercepted traffic in the browser console instead.