@equinor/fusion-framework 8.1.0 → 8.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +0,0 @@
1
- # API
2
-
3
- | Export | Owner | Purpose |
4
- | --- | --- | --- |
5
- | `mockFramework<TModules>(configure?)` | `/mock` | Build and initialize a framework instance for a test |
6
- | `FrameworkMockConfigurator<TModules>` | `/mock` | `FrameworkConfigurator` whose outward boundaries are mocked, exposing `msal`, `serviceDiscovery`, `http` and `context` |
7
- | `enableMsalMock(configurator, configure?)` | `-module-msal/mock` | Register the auth module with an in-process MSAL client |
8
- | `msalMockModule` | `-module-msal/mock` | The auth module with a mock client, for manual registration |
9
- | `MsalMockConfigurator` | `-module-msal/mock` | `MsalConfigurator` backed by a mock client (`setAccount`, `setClient`, …) |
10
- | `MsalMockClient(config)` | `-module-msal/mock` | Build the in-process MSAL client on its own, from the same `MsalClientConfig` as `MsalClient` |
11
- | `createMsalMockClient(config, user?)` | `-module-msal/mock` | Convenience alias for `new MsalMockClient(config)`, optionally signing a user in |
12
- | `createMockToken(claims?)` | `-module-msal/mock` | Mint a deterministic JWT |
13
- | `mockServiceDiscovery(configurator, options?, configure?)` | `-module-service-discovery/mock` | Replace service discovery with an in-memory registry |
14
- | `enableServiceDiscoveryMock(configurator, configure?)` | `-module-service-discovery/mock` | Register the discovery module with an in-memory registry |
15
- | `ServiceDiscoveryMockConfigurator` | `-module-service-discovery/mock` | `ServiceDiscoveryConfigurator` that builds an in-memory registry (`setBaseUri`, `addService`, …) |
16
- | `ServiceDiscoveryMockClient(options?)` | `-module-service-discovery/mock` | Build the in-memory discovery client on its own |
17
- | `defaultServiceDiscoveryMockServices` | `-module-service-discovery/mock` | Baseline services a Fusion app resolves at start-up |
18
- | `createMockService(service, baseUri?)` | `-module-service-discovery/mock` | Expand a sparse service declaration into a full `Service` |
19
- | `configureHttp(configure)` | `-module-http` | Build a module config that pairs `configureClient`/`configureHttpClient` and `addMiddleware` for `configurator.addConfig(...)` — the real module, no mock client |
20
- | `createOpenApiMockMiddleware(mock)` | `-module-http/mock` | Adapt an `@equinor/fusion-openapi-mock` instance into an `HttpMiddleware`, for use with `configurator.http.addMiddleware(...)` |
21
- | `enableContextMock(configurator, configure?)` | `-module-context/mock` | Register the context module with an in-memory seeded pool |
22
- | `contextMockModule` | `-module-context/mock` | The context module with a mock client, for manual registration |
23
- | `ContextMockConfigurator` | `-module-context/mock` | `ContextModuleConfigurator` backed by in-memory context items (`setCurrentContext`, `setContexts`, `addContext`, `setRelatedContexts`, `setResolver`) |
24
-
25
- Module-owned exports are re-exported from `@equinor/fusion-framework/mock` for convenience; importing them from their own package is equally valid.
@@ -1,90 +0,0 @@
1
- # Choose a Fusion testing layer
2
-
3
- Start with the smallest layer that initializes the production behavior the test needs. Fusion
4
- test utilities substitute external boundaries; Vitest still owns tests, assertions, lifecycle
5
- hooks, spies, timers, coverage, and general module mocking.
6
-
7
- ## Which import should I use?
8
-
9
- | Testing intent | Start with | Import |
10
- | --- | --- | --- |
11
- | Pure function or framework-independent hook | Standard Vitest | `vitest` |
12
- | React hook that consumes app modules | `renderAppHook` | `@equinor/fusion-framework-vitest-plugin-react-app` |
13
- | React component, route, or complete Fusion app | Extended `test` and `render` fixture | `@equinor/fusion-framework-vitest-plugin-react-app/test` |
14
- | Reusable app fixture without automatic app-file resolution | `testApp` | `@equinor/fusion-framework-vitest-plugin-react-app` |
15
- | Application module configuration without React | `mockAppModules` | `@equinor/fusion-framework-app/mock` |
16
- | Parent framework or portal-level modules | `mockFramework` | `@equinor/fusion-framework/mock` |
17
- | One module in a bespoke module graph | That module's `enable*Mock` helper | `@equinor/fusion-framework-module-*/mock` |
18
- | Named HTTP clients and a few deterministic responses | `configurator.http.addMiddleware` | Real HTTP configurator |
19
- | Many HTTP operations described by OpenAPI | `createOpenApiMockMiddleware` | `@equinor/fusion-framework-module-http/mock` |
20
- | One method call, timer, global, or JavaScript module | Vitest's `vi` APIs | `vitest` |
21
-
22
- ## React app tests
23
-
24
- Use `@equinor/fusion-framework-vitest-plugin-react-app/test` for the normal application path.
25
- Its `test` and `render` exports resolve the app's manifest, `app.config.ts`, and module
26
- configurator, then initialize a fresh framework and app scope for each test.
27
-
28
- ```tsx
29
- import { expect } from 'vitest';
30
- import { test } from '@equinor/fusion-framework-vitest-plugin-react-app/test';
31
- import { App } from './App';
32
-
33
- test('renders the app', async ({ render }) => {
34
- const screen = await render(<App />);
35
- await expect.element(screen.getByRole('heading')).toBeVisible();
36
- });
37
- ```
38
-
39
- Use the root entry point's `renderAppHook`, `renderAppComponent`, or `testApp` only when the
40
- test should provide `env`, `configure`, or a parent `fusion` instance explicitly. See the
41
- [`@equinor/fusion-framework-vitest-plugin-react-app` documentation](../../vitest-plugin/react-app/README.md).
42
-
43
- ## Framework and module tests
44
-
45
- Use `mockFramework` when code consumes the parent framework directly or when the test needs
46
- to seed several built-in framework modules together:
47
-
48
- ```ts
49
- import { mockFramework } from '@equinor/fusion-framework/mock';
50
-
51
- const fusion = await mockFramework((configurator) => {
52
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
53
- configurator.context.setCurrentContext({
54
- id: 'project-a',
55
- title: 'Project A',
56
- type: { id: 'ProjectMaster' },
57
- value: {},
58
- });
59
- configurator.serviceDiscovery.addService({ key: 'catalog' });
60
- });
61
- ```
62
-
63
- Use a module-owned `/mock` entry point when assembling a custom module graph or testing the
64
- module without the standard framework set. Module mocks keep the real provider,
65
- configurator, validation, and lifecycle; only the client or data source that leaves the
66
- process is substituted.
67
-
68
- ## Choosing how to fake data
69
-
70
- | Need | Preferred boundary | Why |
71
- | --- | --- | --- |
72
- | One known context item | `enableContextMock` | Seeds domain data without transport setup |
73
- | Context service integration | HTTP middleware | Exercises service discovery, HTTP, and context client behavior together |
74
- | One or two HTTP routes | Hand-written `HttpMiddleware` | Keeps the response behavior explicit |
75
- | An API described by OpenAPI | `createOpenApiMockMiddleware` | Generates deterministic responses for the whole specification |
76
- | An individual client call | `vi.spyOn` | Uses the test runner's call assertions and reset semantics |
77
-
78
- An HTTP middleware handles only matching requests. Calling `next(uri, init)` continues to the
79
- next middleware and eventually the real network. Tests intended to be fully offline should
80
- fail or answer every request they expect.
81
-
82
- ## Related package documentation
83
-
84
- - [Framework mock usage](testing.md)
85
- - [Framework mock design](testing-design.md)
86
- - [Framework mock exports](testing-api.md)
87
- - [Add a mock for a custom module](testing-extending.md)
88
- - [Application module testing](../../app/docs/testing.md)
89
- - [HTTP testing](../../modules/http/docs/testing.md)
90
- - [Migrate an existing app to Fusion Vitest](../../vitest-plugin/react-app/docs/migrating-an-existing-app.md)
@@ -1,63 +0,0 @@
1
- # Design
2
-
3
- Why this entry point exists, what it substitutes, and the rules that keep it from growing into a mocking library.
4
-
5
- ## Why this exists
6
-
7
- Fusion Framework cannot start without authenticating a user and resolving services from a registry. Both reach outside the process, so an application test either has to supply real credentials or hand-build a replacement for every built-in module.
8
-
9
- This entry point removes that work. It runs the **real** configure → initialize pipeline with the **real** built-in modules, and substitutes only the boundaries that leave the process.
10
-
11
- That distinction matters: module wiring, configuration validation and lifecycle hooks behave exactly as they do in production, so a test still catches wiring mistakes.
12
-
13
- ## How mocks are organised
14
-
15
- > [!IMPORTANT]
16
- > This entry point contains **no mock logic**. Every module owns and exports its own test double from a `./mock` entry point. This entry point only composes the built-in set.
17
-
18
- ```
19
- @equinor/fusion-framework-module-msal/mock -> enableMsalMock
20
- @equinor/fusion-framework-module-service-discovery/mock -> mockServiceDiscovery
21
- @equinor/fusion-framework/mock -> composes the above
22
- ```
23
-
24
- Three consequences follow, and they are the reason for the split:
25
-
26
- - **Mocks cannot drift.** A test double lives beside the implementation it stands in for, so a change to the real interface breaks it in the same package, in the same build.
27
- - **Mocks version with their module.** Installing msal `v10` gets msal `v10`'s mock. There is no version matrix to reconcile.
28
- - **Application modules work the same way.** A team's own module exposes its own mock entry point and composes without this entry point knowing it exists.
29
-
30
- Adding mock support to another Fusion module means adding a `src/mock/` folder to that module — not editing this entry point.
31
-
32
- ## What is actually substituted
33
-
34
- > [!IMPORTANT]
35
- > Only the **client** — the object that performs network I/O — is replaced. Providers, configurators, schema validation and module `initialize` are all real.
36
-
37
- For authentication this means `MsalProvider` itself runs. A test therefore observes real provider behaviour, including decisions the provider makes on the caller's behalf:
38
-
39
- ```typescript
40
- const fusion = await mockFramework((configurator) => {
41
- configurator.msal.setClientConfig({ auth: { clientId: 'my-app', tenantId: 'my-tenant' } });
42
- });
43
-
44
- const token = await fusion.modules.auth.acquireAccessToken();
45
- // scope is 'my-app/.default' — resolved by the real provider, not by the test double
46
- ```
47
-
48
- A double that replaced the provider would have skipped that logic and quietly reported whatever it was told to.
49
-
50
- ## Determinism
51
-
52
- Tokens and resolved services are identical across runs and across machines. `createMockToken` uses a fixed issue time, so a token can be compared or snapshotted directly.
53
-
54
- ## Test-runner support
55
-
56
- The package has **no test-runner dependency**. It builds a real framework instance and returns it; assertions are the caller's concern. It works under Vitest today and would work unchanged under another runner.
57
-
58
- `vitest` appears only in `devDependencies`, for this entry point's own tests.
59
-
60
- > [!IMPORTANT]
61
- > This is deliberate, and it is why there is **no Fusion mocking API**. The framework's job is making the runtime *substitutable* — a real configurator that validates, an I/O-boundary client that needs no network or credentials, a registry you compose on the builder. Replacing an individual call is your runner's job, and it does it better: call assertions, argument matchers and reset semantics you already know.
62
- >
63
- > Mock clients are therefore plain classes with ordinary methods. `vi.spyOn`, `bun:test`'s `spyOn` and Node's `t.mock.method` all work on them directly.
@@ -1,102 +0,0 @@
1
- # Adding a mock for another module
2
-
3
- How to give a module a test double, whether it is one of ours or one of yours.
4
-
5
- ## Mocking your own module
6
-
7
- An application module needs no support from this entry point. Ship a test double next to the module and apply it alongside the framework mocks.
8
-
9
- Two seams matter, and the second is the one teams miss:
10
-
11
- 1. **A client seam** — `setClient`, the way `MsalConfigurator.setClient` and `setServiceDiscoveryClient` do — so the object performing I/O can be swapped.
12
- 2. **Configuration on the builder** — so a test can adjust behaviour *without* constructing a client at all. `ServiceDiscoveryMockConfigurator.addService` is the reference: the configurator accumulates config, and the client is built from it when the module assembles its config.
13
-
14
- ```typescript
15
- // @my-app/module-invoices/mock
16
- export const mockInvoices = (configurator, options = {}) => {
17
- configurator.addConfig(
18
- configureInvoices((builder) => {
19
- builder.setClient({
20
- getInvoice: async (id) => ({ id, total: options.total ?? 100 }),
21
- });
22
- }),
23
- );
24
- };
25
- ```
26
-
27
- ```typescript
28
- // @my-app/module-invoices — the module's own type, exported for tests
29
- export type InvoiceModule = Module<'invoices', InvoiceClient, InvoiceConfigurator>;
30
- ```
31
-
32
- Pass that descriptor to `mockFramework` as a type argument, and the module is typed on both the configurator and the returned instance:
33
-
34
- ```typescript
35
- const fusion = await mockFramework<[InvoiceModule]>((configurator) => {
36
- enableInvoicesMock(configurator, { total: 42 });
37
- });
38
-
39
- await fusion.modules.invoices.getInvoice('inv-1'); // typed, no cast
40
- ```
41
-
42
- ### Giving your module the same accessor as `.msal` and `.serviceDiscovery`
43
-
44
- `enableInvoicesMock(configurator, options)` is enough on its own — the configurator it builds is discarded once configuration runs, which is fine when a test only ever sets options up front. Reach for an accessor when a test needs the configurator itself, for example to assert against it after the fact.
45
-
46
- Subclass `FrameworkMockConfigurator` and use the protected `_pin`/`_getConfig` pair it exposes for exactly this — the same mechanism `.msal` and `.serviceDiscovery` are built from:
47
-
48
- ```typescript
49
- class AppMockConfigurator extends FrameworkMockConfigurator<[InvoiceModule]> {
50
- constructor() {
51
- super();
52
- this._pin(invoiceMockModule);
53
- }
54
-
55
- get invoices(): InvoiceMockConfigurator {
56
- return this._getConfig('invoices');
57
- }
58
- }
59
- ```
60
-
61
- `_pin` replaces the module's own `configure` factory with one that always returns the same instance — pinning it before initialization runs is what lets a test reach `.invoices` synchronously and have it be the configurator the module is actually built from. `_getConfig` looks that instance up by name, throwing if nothing was pinned for it.
62
-
63
- ## What is not covered yet
64
-
65
- `.services` and `.telemetry` are already reachable on `FrameworkMockConfigurator` — their `configure` factories take no `ref`, so they were safe to pin the same way `.msal` and `.serviceDiscovery` are. What is missing is a test double behind them: neither module has a `src/mock/` folder yet, so anything issuing an actual request through them still reaches the network. Adding one means creating that folder in **that module**, then pinning its mock configurator with `_pin` and exposing it with `_getConfig` on `FrameworkMockConfigurator`, replacing the real module descriptor pinned there today.
66
-
67
- `event` is not pinned at all, deliberately: its `configure` factory reads `ref` to wire event bubbling to a parent event provider when `FrameworkMockConfigurator` is hoisted inside a host framework. Pinning would call `configure()` with `ref` always `undefined`, silently breaking that bubbling — so it is left to build the normal way, from the module system's own configure phase, where `ref` is actually known.
68
-
69
- ## `.http`
70
-
71
- `.http` is the real `HttpClientConfigurator` built from `@equinor/fusion-framework-module-http` — there is no separate mock client. Register a short-circuiting middleware through `addMiddleware` to answer requests without touching the network:
72
-
73
- ```typescript
74
- configurator.http.configureClient('catalog', { baseUri: 'https://api.example.com' });
75
- configurator.http.addMiddleware(async (uri, init, next) =>
76
- uri === 'https://api.example.com/items' ? Response.json([{ id: 1 }]) : next(uri, init),
77
- );
78
-
79
- const items = await fusion.modules.http.createClient('catalog').json('/items');
80
- ```
81
-
82
- A middleware is `(uri, init, next) => Response | Promise<Response> | Observable<Response>` — return a `Response` to answer the request, or call and return `next(uri, init)` to fall through to whichever middleware (or the real network call) is registered next. Two ways to fill that seam:
83
-
84
- - **`addMiddleware`** — a hand-rolled middleware for a handful of routes, as above.
85
- - **`createOpenApiMockMiddleware`** (`@equinor/fusion-framework-module-http/mock`) — adapts an `@equinor/fusion-openapi-mock` instance (`createOpenApiMock(document)`), so a real `openapi.json`/`openapi.yaml` fakes every response with no handlers written at all until an edge case needs overriding.
86
-
87
- ## `.context`
88
-
89
- `.context` is backed by `ContextMockConfigurator` (`@equinor/fusion-framework-module-context/mock`): context items live in an in-memory pool instead of a real context API, so a test needs no HTTP mock and no service-discovery mock to seed a known item.
90
-
91
- ```typescript
92
- configurator.context.setCurrentContext({ id: 'my-ctx', type: { id: 'ProjectMaster' }, value: {} });
93
-
94
- fusion.modules.context.currentContext; // the seeded item, resolved on startup
95
- ```
96
-
97
- Real `ContextProvider` behaviour — `validateContext`, `resolveContext`, parent-context propagation — still runs against the seeded data; only the data source is substituted. Two layers cover different needs:
98
-
99
- - **`setCurrentContext`/`setContexts`/`addContext`/`setRelatedContexts`** — a friendly, context-domain vocabulary for the common case: seed a known item, get it back.
100
- - **`setResolver`** — an escape hatch for a custom `resolveContext` strategy or a shape the friendly layer did not anticipate.
101
-
102
- Seeding a context item this way is one of two ways to fake context in a test — the other is mocking the context API's HTTP responses directly (with `.http`, optionally paired with `createOpenApiMockMiddleware`), which exercises the real `ContextModuleConfigurator`/services/HTTP pipeline instead of substituting it. Reach for `.context` to seed one known item with no transport involved; reach for `.http` when the test needs to cover that pipeline itself.
package/docs/testing.md DELETED
@@ -1,178 +0,0 @@
1
- # Usage
2
-
3
- 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.
4
-
5
- `mockFramework` takes a single callback, which receives a [`FrameworkMockConfigurator`](#the-configurator). That configurator **is** a `FrameworkConfigurator`, so everything an application does at configure time works here unchanged.
6
-
7
- ## Zero configuration
8
-
9
- ```typescript
10
- import { mockFramework } from '@equinor/fusion-framework/mock';
11
-
12
- const fusion = await mockFramework();
13
-
14
- fusion.modules.auth.account?.name; // 'Test User'
15
- await fusion.modules.serviceDiscovery.resolveService('apps'); // resolves offline
16
- ```
17
-
18
- Every module declared by `FrameworkConfigurator` is initialized: `event`, `auth`, `http`, `serviceDiscovery`, `context` and `telemetry`.
19
-
20
- ## The configurator
21
-
22
- Modules whose boundary is mocked expose their mock configurator directly, so a test configures them without registering a callback:
23
-
24
- | Property | Type |
25
- | --- | --- |
26
- | `msal` | `MsalMockConfigurator` |
27
- | `serviceDiscovery` | `ServiceDiscoveryMockConfigurator` |
28
- | `http` | `IHttpClientConfigurator` (the real configurator — see [`.http`](testing-extending.md#http)) |
29
- | `context` | `ContextMockConfigurator` |
30
- | `telemetry` | `TelemetryMockConfigurator` |
31
-
32
- ```typescript
33
- const fusion = await mockFramework((configurator) => {
34
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
35
- configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
36
- });
37
- ```
38
-
39
- These are the real module configurators, so the real builder API, the real validation and the real provider are used.
40
-
41
- ## Choosing the signed-in user
42
-
43
- ```typescript
44
- const fusion = await mockFramework((configurator) => {
45
- configurator.msal.setAccount({ name: 'Ada Lovelace', username: 'ada@equinor.com' });
46
- });
47
-
48
- const token = await fusion.modules.auth.acquireAccessToken({
49
- request: { scopes: ['Files.Read'] },
50
- });
51
- ```
52
-
53
- The 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.
54
-
55
- `setAccount` records configuration only — the user is signed in on the client before the provider initializes. It takes an object, `null` when nobody is signed in, or an ordinary config-builder callback resolving either:
56
-
57
- ```typescript
58
- configurator.msal.setAccount(null);
59
- configurator.msal.setAccount(async ({ hasModule }) => ({
60
- name: hasModule('app') ? 'App User' : 'Portal User',
61
- }));
62
- ```
63
-
64
- Because the user is in place before `MsalProvider.initialize()` runs, the provider's real start-up path acts on it — pair `signedOut` with `setRequiresAuth(true)` to watch the automatic login happen.
65
-
66
- The account replaces any previously declared account, and is signed in on whichever client the module authenticates through — the one it builds, one supplied through `setClient`, or the host's when the module is hoisted onto a host application's provider. When that client cannot represent a declared user, it throws rather than failing quietly.
67
-
68
- ## Testing signed-out behaviour
69
-
70
- ```typescript
71
- const fusion = await mockFramework((configurator) => {
72
- configurator.msal.setAccount({ signedOut: true });
73
- });
74
-
75
- fusion.modules.auth.account; // null
76
- ```
77
-
78
- Silent flows then resolve empty so the provider follows its unauthenticated path, while an explicit `login()` still succeeds — so a test can drive the sign-in journey, not only its end state.
79
-
80
- ## Composing the service registry
81
-
82
- 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.
83
-
84
- ```typescript
85
- const fusion = await mockFramework((configurator) => {
86
- configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
87
- configurator.serviceDiscovery.addService({ key: 'my-api' });
88
- configurator.serviceDiscovery.removeService('bookmarks');
89
- });
90
- ```
91
-
92
- To replace the baseline registry outright rather than compose onto it, use `setServices`:
93
-
94
- ```typescript
95
- configurator.serviceDiscovery.setServices([{ key: 'apps', uri: 'http://localhost:3000' }]);
96
- ```
97
-
98
- 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.
99
-
100
- > [!WARNING]
101
- > 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.
102
-
103
- ## Seeding context
104
-
105
- ```typescript
106
- const fusion = await mockFramework((configurator) => {
107
- configurator.context.setCurrentContext({ id: 'project-42', type: { id: 'ProjectMaster' }, value: {} });
108
- });
109
-
110
- fusion.modules.context.currentContext; // the seeded item
111
- ```
112
-
113
- `configurator.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.
114
-
115
- 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.
116
-
117
- ## Mocking an individual call
118
-
119
- 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.
120
-
121
- ```typescript
122
- vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(service);
123
-
124
- afterEach(() => vi.restoreAllMocks());
125
- ```
126
-
127
- 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.
128
-
129
- ## Configuring the framework as an application does
130
-
131
- The configurator is a `FrameworkConfigurator`, so every `enableX` and `configureX` helper is available and behaves normally.
132
-
133
- ```typescript
134
- const fusion = await mockFramework((configurator) => {
135
- configurator.onConfigured(() => {
136
- /* ... */
137
- });
138
- });
139
- ```
140
-
141
- Mocks are registered **before** the callback runs, so anything configured there wins — including replacing a mock with a different one.
142
-
143
- ## Registering your own modules
144
-
145
- 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.
146
-
147
- ```typescript
148
- const fusion = await mockFramework<[InvoiceModule]>((configurator) => {
149
- enableInvoicesMock(configurator, { total: 42 });
150
- });
151
-
152
- await fusion.modules.invoices.getInvoice('inv-1'); // fully typed
153
- ```
154
-
155
- `addModule` is available if it reads better at the call site; it is sugar for the same call.
156
-
157
- ```typescript
158
- configurator.addModule((c) => enableInvoicesMock(c, { total: 42 }));
159
- ```
160
-
161
- > [!NOTE]
162
- > 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`.
163
-
164
- See [Adding a mock for another module](./testing-extending.md) for how to give your module a test double, and a property on the configurator.
165
-
166
- ## Bringing your own configurator
167
-
168
- `FrameworkMockConfigurator` can be constructed directly and initialized with `init`, which is useful when a test needs to hold on to the configurator.
169
-
170
- ```typescript
171
- import { init } from '@equinor/fusion-framework';
172
- import { FrameworkMockConfigurator } from '@equinor/fusion-framework/mock';
173
-
174
- const configurator = new FrameworkMockConfigurator();
175
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
176
-
177
- const fusion = await init(configurator);
178
- ```