function overrideCapability
Since 0.6.0
overrideCapability(
token: CapabilityToken,
service: object
): IPlugin

Creates a plugin that REPLACES an already-provided capability with a test double, leaving the rest of the application's composition intact.

It is ordered after the provider and before ordinary consumers, so it reaches a consumer that resolves the capability during its own register() as well as one that resolves it per request.

This is the replacement mechanism AI_GUIDELINES §3.4 describes — "a replacement plugin registers the same capability token with override: true" — with the three constraints that make it work applied for you:

  1. It declares no provides. A second plugin declaring a token the real one already declares is refused by the kernel before any of them runs (Capability 'x' is provided by both 'a' and 'b'), which is why createMockPlugin cannot be used to override.
  2. It registers with { override: true }, without which the kernel refuses a second registration of a live token.
  3. It is ordered after the provider and before ordinary consumers, which is what lets it reach a consumer that resolves the capability during its OWN register()NotificationPlugin does exactly that. Ordering comes from an optionalDependencies edge on the token (the resolver visits a dependency first, whatever its priority band) plus an early priority, so the depth-first sort reaches this plugin, and through the edge its provider, before a PLUGIN_PRIORITY.NORMAL consumer.

What it still cannot do: undo the provider's eager side effects. The real plugin's register() has run by the time this one replaces its service, so a database adapter's connect() or a broker's dial has already happened. Only removing the plugin prevents that — createTestApp({ app, without }), or app.unregister(name) directly.

It requires the provider to declare the token in provides. That is what the ordering edge hangs on, and it is how a plugin is depended upon at all. A plugin that registers a capability without declaring it cannot be ordered against, so the override registers first and the provider's own plain registration then fails the application's startup with Capability '<token>' is already registered. Declare provides, or use the removal form below.

To remove the provider instead of replacing it — which also prevents its eager side effects — supply the double as a provider ahead of its consumers:

await createTestApp({
  app: createApp(),
  without: ['mail-plugin'],
  overrides: [createMockPlugin({
    name: 'mail-plugin',
    provides: CAPABILITIES.MAIL,
    service: fakeMailer,
    priority: PLUGIN_PRIORITY.HIGH,   // ahead of the consumer that captures it
  })],
});

Examples

Example 1

import { createTestApp, overrideCapability } from '@setu-ts/testing';
import { CAPABILITIES } from '@setu-ts/common';
import { createApp } from '../setu.config.ts';

const app = await createTestApp({
  app: createApp(),
  overrides: [overrideCapability(CAPABILITIES.MAIL, { send: () => Promise.resolve() })],
});

Parameters

token: CapabilityToken

The capability token to replace. Must already be provided.

service: object

The test double to register under it

Return Type

IPlugin

A plugin to append to createTestApp's overrides, or to pass to app.register() on an un-started application Multi-provider capabilities cannot be overridden, and are refused. A token registered with { multi: true } — the kernel's health-indicator, metric-registration, openapi-schema, decorator-handler and cli-command, and any an application registers itself — has no single provider to replace: getAll returns the single and multi registrations concatenated, so an override would add a provider while every real one kept running. Detection is generic rather than a list of known tokens, so an application's own multi capability is refused too. Exclude the plugin that registers the provider instead.

Throws

Error

During start() — from an onInit hook, not from register() — if nothing provides token, or if token is a multi-provider capability. Both checks run there because this plugin registers EARLY: at its own register() a multi-provider capability has not accumulated its providers yet, and a token it does not shadow may still be registered by a later plugin. A silent no-op would leave the real service serving while the test reported success.

Usage

import { overrideCapability } from "testing/src/index.ts";