Dev Server - Mock Services
Develop with mock services
Use @equinor/fusion-framework-cli-plugin-mock-server when a Fusion application needs a local,
deterministic, or not-yet-deployed backend. The plugin adds ffc mock-server; it does not change
the base dev-server runtime or start a background process automatically.
The recommended application workflow has three parts:
- Create
mocks/<service>.mock.tswithdefineService. - Run
ffc mock-serverin a foreground terminal. - Run
ffc app devfor real discovery plus local overrides, orffc app dev --mockfor an
isolated mock environment.
Important
Keep service mock behavior in <service>.mock.ts, not dev-server.config.ts. The executable
module is reusable by local development, Playwright, and the programmatic mock server. Reserveapi.routes and api.processServices for advanced server infrastructure and discovery
transformations that are not service mocks.
Migrate existing dev-server configuration
Migrate one service at a time. Move behavior owned by a backend service intomocks/<service>.mock.ts; keep host-level behavior in dev-server.config.ts.
| Existing configuration | New location | Use when |
|---|---|---|
api.processServices adds or redirects one service | serviceDiscovery in <service>.mock.ts | The change exists only to make that service available locally. |
api.routes returns a mocked OpenAPI operation | routes in <service>.mock.ts | The path and method belong to the service's OpenAPI contract. |
api.routes implements request-aware service behavior | middleware in <service>.mock.ts | The behavior needs request headers, parameters, or a parsed body. |
| Schema faker overrides | components in <service>.mock.ts | Generated OpenAPI responses need deterministic field values. |
| Filtering or rewriting real discovery for the whole host | Keep api.processServices | The transformation is infrastructure behavior rather than a service mock. |
| Dev-server health checks or other host-owned endpoints | Keep api.routes | The route belongs to the local host, not to one backend service. |
For example, a legacy configuration might replace the generated proxy route for an existinginventory service with a local response:
// dev-server.config.ts
import { defineDevServerConfig, processServices } from '@equinor/fusion-framework-cli/dev-server';
export default defineDevServerConfig(() => ({
api: {
processServices: (services, args) => {
const processed = processServices(services, args);
return {
...processed,
routes: [
...processed.routes.filter((route) => route.match !== '/inventory/api*sub'),
{
match: '/inventory/api*sub',
middleware: (_request, response) => {
response.setHeader('content-type', 'application/json');
response.end(JSON.stringify([{ id: 'local-item', name: 'Local item' }]));
},
},
],
};
},
},
}));Move that service-owned response into an executable mock module. Paths in routes are relative to
the service and must match an operation in its OpenAPI document:
// mocks/inventory.mock.ts
import schema from './inventory.openapi.json' with { type: 'json' };
import { defineService } from '@equinor/fusion-openapi-mock-server/discovery';
export default defineService({
key: 'inventory',
serviceDiscovery: 'replace',
schema,
routes: {
'/items': {
get: {
mock: [{ id: 'local-item', name: 'Local item' }],
},
},
},
});'replace' is appropriate because the module carries a complete schema and deliberately replaces
the real discovery entry during normal development or a same-key preset in isolated mode. Use'merge' only when a selected preset or earlier local mock layer already supplies the service and
schema; the standalone mock server never inherits schemas from remote discovery. Use 'new' with
a schema for a pre-production service, or false with a schema for a direct-only endpoint.
Complete the migration:
- Install
@equinor/fusion-framework-cli-plugin-mock-serverand@equinor/fusion-openapi-mock-serveras development dependencies. - Start
ffc mock-serverin a foreground terminal. - Run
ffc app devto combine real discovery with local definitions, or useffc app dev --mock http://localhost:4010for an isolated environment. - Verify the migrated service, then remove only its old
api.routesandapi.processServices
branches. Leave unrelated host-level configuration in place.
During an incremental migration, migrated and legacy services can coexist. Do not define the same
service behavior in both places; route precedence can hide which implementation handled a request.
Install and start the mock server
pnpm add -D @equinor/fusion-framework-cli-plugin-mock-server
ffc mock-serverImportant
Keep this foreground process running in one terminal and start the application in another. The
plugin intentionally does not create an unowned background process.
Check that startup completed before connecting the app:
curl http://localhost:4010/@fusion-mock/healthTips
Use the health endpoint as the url readiness check in Playwright's webServer configuration.
Define one executable module per service
mocks/
inventory.mock.ts
inventory.openapi.jsonimport schema from './inventory.openapi.json' with { type: 'json' };
import { defineService } from '@equinor/fusion-openapi-mock-server/discovery';
export default defineService({
key: 'inventory',
serviceDiscovery: 'new',
schema,
components: {
InventoryItem: { name: () => 'Local item' },
},
});The module keeps service ownership, schema, deterministic fields, routes, and middleware in one
retrieval-friendly place.
This example uses 'new' because inventory is not registered yet; startup fails if the key later
appears in real discovery. Use 'merge' only when a selected preset or earlier local mock layer
already provides the service schema and only selected local behavior should change.
Choose the development mode
Combine real discovery with selected local services
ffc mock-server
ffc app devNormal development fetches real discovery and overlays discovery-visible local definitions by
service key. Use this when most real backends remain useful.
Use only predefined and local mocks
ffc mock-server
ffc app dev --mock http://localhost:4010--mock points application discovery at the standalone server. The mock server never fetches real
service discovery; it resolves only bundled presets and local modules. Use this for isolated
development, CI, and browser tests.
Warning
--mock is intentionally isolated. A service missing from the bundled presets and local modules
will not fall back to remote service discovery.
A preset is a built-in group of service definitions. The default fusion preset supplies common
services that framework modules resolve during startup. Local modules are layered after presets,
so app-specific behavior can override the baseline.
Choose a discovery mode
| Mode | Developer scenario |
|---|---|
'merge' | Override selected behavior of a service supplied by a selected preset or earlier local mock layer while inheriting its schema. Remote discovery alone is not a merge source. |
'new' | Add a pre-production service expected to enter real discovery before release. Definition resolution fails if the key already exists in discovery or an earlier mock layer. |
'replace' | Supply a complete local definition and deliberately replace an earlier same-key definition. |
false | Serve an app-owned endpoint without advertising it through discovery. Configure its <key>.localhost mock URL directly in environment-specific app config. |
Caution
Use 'new' only as a temporary pre-production contract. Once the real service key appears,
definition resolution fails on purpose; register the backend before release and remove or change
the local definition.
For example, a service with key my-api and serviceDiscovery: false is still served athttp://my-api.localhost:4010; it is simply absent from /@fusion-mock/discovery. This mirrors an
application-owned API URL that production configuration supplies directly.
Register that URL through the app's environment-specific endpoints configuration. The
HTTP-client guide
contains the complete app.config.local.ts recipe and restart caveat.
Configure shared defaults
import type {} from '@equinor/fusion-framework-cli-plugin-mock-server';
import { defineDevServerConfig } from '@equinor/fusion-framework-cli';
export default defineDevServerConfig(() => ({
mockServer: {
path: 'mocks',
host: 'localhost',
port: 4010,
seed: 42,
},
}));Command-line flags override these defaults. path is relative to the project root.
Test in a real browser
Use Playwright's webServer array to own both foreground processes and stop them after the suite.
The mock server exposes HTTP endpoints for per-test operation overrides and reset.
See the plugin reference, OpenAPI mock-server guide, and Playwright cookbook.