Framework testing reference
Usage
Recipes for the situations a test normally runs into. Every one of them builds a real framework instance — only the boundaries that leave the process are substituted.
mockFramework takes a single callback, which receives a FrameworkMockConfigurator. That configurator is a FrameworkConfigurator, so everything an application does at configure time works here unchanged.
Zero configuration
import { mockFramework } from '@equinor/fusion-framework/mock';
const fusion = await mockFramework();
fusion.modules.auth.account?.name; // 'Test User'
await fusion.modules.serviceDiscovery.resolveService('apps'); // resolves offlineEvery module declared by FrameworkConfigurator is initialized: event, auth, http, serviceDiscovery, context and telemetry.
The configurator
Modules whose boundary is mocked expose their mock configurator directly, so a test configures them without registering a callback:
| Property | Type |
|---|---|
msal | MsalMockConfigurator |
serviceDiscovery | ServiceDiscoveryMockConfigurator |
http | IHttpClientConfigurator (the real configurator — see .http) |
context | ContextMockConfigurator |
telemetry | TelemetryMockConfigurator |
const fusion = await mockFramework((configurator) => {
configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
});These are the real module configurators, so the real builder API, the real validation and the real provider are used.
Configuring token acquisition
import { createMockToken, mockFramework } from '@equinor/fusion-framework/mock';
const fusion = await mockFramework((configurator) => {
configurator.msal.setAcquireToken(({ scopes, clientId }) =>
createMockToken({
aud: clientId,
scp: scopes.join(' '),
name: 'Ada Lovelace',
preferred_username: 'ada@equinor.com',
}),
);
});
const token = await fusion.modules.auth.acquireAccessToken({
request: { scopes: ['Files.Read'] },
});The acquired token is a structurally valid JWT carrying the claims an application reads. It is unsigned by default and must never be accepted by anything but a test. The MSAL mock derives its active account from those same claims, so the token and account cannot represent different users.
Composing the service registry
Nothing has to be constructed to add a service, or to move every service to a locally running mock server such as Mockoon or Prism — in which case the application makes real HTTP calls with nothing intercepting them.
const fusion = await mockFramework((configurator) => {
configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
configurator.serviceDiscovery.addService({ key: 'my-api' });
configurator.serviceDiscovery.removeService('bookmarks');
});To replace the baseline registry outright rather than compose onto it, use setServices:
configurator.serviceDiscovery.setServices([{ key: 'apps', uri: 'http://localhost:3000' }]);By default an undeclared service resolves to a synthesised entry rather than throwing, so a test does not fail merely because the application resolved something the test did not think to declare. Call setResolveUnknownServices(false) to assert the opposite.
Warning
Built-in modules resolve services while the framework starts — the context module resolves context, for example. Combining setResolveUnknownServices(false) with a setServices registry that omits them fails initialization rather than the assertion you were writing. Either keep synthesis on, or declare every service the framework itself needs.
Seeding context
const fusion = await mockFramework((configurator) => {
configurator.context.setCurrentContext({ id: 'project-42', type: { id: 'ProjectMaster' }, value: {} });
});
fusion.modules.context.currentContext; // the seeded itemconfigurator.context is a ContextMockConfigurator — a small, context-domain vocabulary (setCurrentContext, setContexts, addContext, setRelatedContexts) covers seeding a known item with no HTTP mock and no service-discovery mock required. setResolver is the escape hatch for a custom resolution need the friendly methods do not cover. Real ContextProvider behaviour — validateContext, resolveContext, parent-context propagation — still runs against the seeded data in both.
This is one of two ways to fake context data: seeding an item directly (above) substitutes only the data source, with no transport involved. Mocking the context API's HTTP responses instead, through .http, exercises the real ContextModuleConfigurator/services/HTTP pipeline — reach for that when the test needs to cover that pipeline itself, optionally paired with createOpenApiMockMiddleware for faker-generated data straight from context's OpenAPI spec.
Mocking an individual call
That is your test runner's job, not this entry point's. Mock clients are plain classes with ordinary methods, so any runner can spy on them with its own tooling — including call assertions and its own reset semantics.
vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(service);
afterEach(() => vi.restoreAllMocks());The same holds for bun:test's spyOn and Node's t.mock.method, which is why this entry point introduces no mocking API of its own.
Configuring the framework as an application does
The configurator is a FrameworkConfigurator, so every enableX and configureX helper is available and behaves normally.
const fusion = await mockFramework((configurator) => {
configurator.onConfigured(() => {
/* ... */
});
});Mocks are registered before the callback runs, so anything configured there wins — including replacing a mock with a different one.
Registering your own modules
Pass your module descriptors as a type argument. They are then typed on both the configurator and the returned instance, so no cast is needed to reach them.
const fusion = await mockFramework<[InvoiceModule]>((configurator) => {
enableInvoicesMock(configurator, { total: 42 });
});
await fusion.modules.invoices.getInvoice('inv-1'); // fully typedaddModule is available if it reads better at the call site; it is sugar for the same call.
configurator.addModule((c) => enableInvoicesMock(c, { total: 42 }));Note
Only modules that ship a mock configurator get a property such as configurator.msal. Everything else is registered exactly as it is in production — through its own enableX helper or configurator.addConfig. Module instances never exist at configure time; they are created by initialize.
See Adding a mock for another module for how to give your module a test double, and a property on the configurator.
Bringing your own configurator
FrameworkMockConfigurator can be constructed directly and initialized with init, which is useful when a test needs to hold on to the configurator.
import { init } from '@equinor/fusion-framework';
import { FrameworkMockConfigurator } from '@equinor/fusion-framework/mock';
const configurator = new FrameworkMockConfigurator();
const fusion = await init(configurator);