Core

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 defaultUrlMatcher so path parameters like :id work 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 global delay option 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

OptionTypeDescription
baseURLstring The origin that the mock server handles (e.g. 'https://api.example.com'). Must start with http. Requests to other origins pass through.
delaynumber Global delay in milliseconds applied to synchronous responses. Async handlers manage their own delay via c.delay().
logging.bypassboolean Log requests that were not matched and passed through to the network.
logging.handlerboolean 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:

PropertyTypeDescription
c.req.bodyBodyType | nullThe parsed request body.
c.req.paramsRecord<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.queryParamsHttpParams Query string parameters. When the Angular HttpClient call supplies HttpParams, those take precedence over the raw URL query string.
c.req.rawHttpRequest<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

  • provideMockServer replaces Angular's HttpBackend with MockServerBackend.
  • On every outgoing request, MockServerBackend asks MockServer whether 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 FetchBackend so 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.