Merge pull request #19654 from backstage/rugvip/service-tester
backend-test-utils: add ServiceFactoryTester utility
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Introduced a new utility for testing service factories, `ServiceFactoryTester`.
|
||||
+23
-46
@@ -14,39 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ServiceFactoryTester,
|
||||
mockServices,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { httpRouterServiceFactory } from './httpRouterServiceFactory';
|
||||
|
||||
describe('httpRouterFactory', () => {
|
||||
it('should register plugin paths', async () => {
|
||||
const rootHttpRouter = { use: jest.fn() };
|
||||
const factory = httpRouterServiceFactory() as any;
|
||||
const rootHttpRouter = mockServices.rootHttpRouter.mock();
|
||||
const tester = ServiceFactoryTester.from(httpRouterServiceFactory, {
|
||||
dependencies: [rootHttpRouter.factory],
|
||||
});
|
||||
|
||||
const handler1 = () => {};
|
||||
const router1 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test1' },
|
||||
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router1.use(handler1);
|
||||
const router1 = await tester.get('test1');
|
||||
router1.use(() => {});
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
'/api/test1',
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
const handler2 = () => {};
|
||||
const router2 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test2' },
|
||||
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router2.use(handler2);
|
||||
const router2 = await tester.get('test2');
|
||||
router2.use(() => {});
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
'/api/test2',
|
||||
@@ -55,37 +45,24 @@ describe('httpRouterFactory', () => {
|
||||
});
|
||||
|
||||
it('should use custom path generator', async () => {
|
||||
const rootHttpRouter = { use: jest.fn() };
|
||||
const factory = httpRouterServiceFactory({
|
||||
getPath: id => `/some/${id}/path`,
|
||||
}) as any;
|
||||
|
||||
const handler1 = () => {};
|
||||
const router1 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test1' },
|
||||
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
|
||||
},
|
||||
undefined,
|
||||
const rootHttpRouter = mockServices.rootHttpRouter.mock();
|
||||
const tester = ServiceFactoryTester.from(
|
||||
httpRouterServiceFactory({
|
||||
getPath: id => `/some/${id}/path`,
|
||||
}),
|
||||
{ dependencies: [rootHttpRouter.factory] },
|
||||
);
|
||||
router1.use(handler1);
|
||||
|
||||
const router1 = await tester.get('test1');
|
||||
router1.use(() => {});
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
'/some/test1/path',
|
||||
expect.any(Function),
|
||||
);
|
||||
|
||||
const handler2 = () => {};
|
||||
const router2 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test2' },
|
||||
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router2.use(handler2);
|
||||
const router2 = await tester.get('test2');
|
||||
router2.use(() => {});
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
'/some/test2/path',
|
||||
|
||||
+21
-42
@@ -14,54 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactoryTester } from '@backstage/backend-test-utils';
|
||||
import { schedulerServiceFactory } from './schedulerServiceFactory';
|
||||
|
||||
describe('schedulerFactory', () => {
|
||||
it('creates sidecar database features', async () => {
|
||||
expect.assertions(3);
|
||||
const tester = ServiceFactoryTester.from(schedulerServiceFactory());
|
||||
|
||||
const subject = schedulerServiceFactory();
|
||||
|
||||
const plugin = createBackendPlugin({
|
||||
pluginId: 'example',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
scheduler: subject.service,
|
||||
database: coreServices.database,
|
||||
},
|
||||
init: async ({ scheduler, database }) => {
|
||||
await scheduler.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: { seconds: 1 },
|
||||
frequency: { seconds: 1 },
|
||||
fn: async () => {},
|
||||
});
|
||||
|
||||
const client = await database.getClient();
|
||||
await expect(
|
||||
client.from('backstage_backend_tasks__tasks').count(),
|
||||
).resolves.toEqual([{ 'count(*)': 1 }]);
|
||||
await expect(
|
||||
client.from('backstage_backend_tasks__knex_migrations').count(),
|
||||
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
|
||||
await expect(
|
||||
client
|
||||
.from('backstage_backend_tasks__knex_migrations_lock')
|
||||
.count(),
|
||||
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
|
||||
},
|
||||
});
|
||||
},
|
||||
const scheduler = await tester.get();
|
||||
await scheduler.scheduleTask({
|
||||
id: 'task1',
|
||||
timeout: { seconds: 1 },
|
||||
frequency: { seconds: 1 },
|
||||
fn: async () => {},
|
||||
});
|
||||
|
||||
await startTestBackend({
|
||||
features: [plugin(), subject],
|
||||
});
|
||||
const database = await tester.getService(coreServices.database);
|
||||
|
||||
const client = await database.getClient();
|
||||
await expect(
|
||||
client.from('backstage_backend_tasks__tasks').count(),
|
||||
).resolves.toEqual([{ 'count(*)': 1 }]);
|
||||
await expect(
|
||||
client.from('backstage_backend_tasks__knex_migrations').count(),
|
||||
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
|
||||
await expect(
|
||||
client.from('backstage_backend_tasks__knex_migrations_lock').count(),
|
||||
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,9 +58,9 @@ const pluginMetadataServiceFactory = createServiceFactory(
|
||||
);
|
||||
|
||||
export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
static create(factories: Array<ServiceFactory>): EnumerableServiceHolder {
|
||||
static create(factories: Array<ServiceFactory>): ServiceRegistry {
|
||||
const registry = new ServiceRegistry(factories);
|
||||
registry.checkForCircularDeps();
|
||||
registry.#checkForCircularDeps();
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
InternalServiceFactory,
|
||||
Promise<unknown>
|
||||
>();
|
||||
readonly #dependencyGraph: DependencyGraph<string>;
|
||||
|
||||
private constructor(factories: Array<ServiceFactory>) {
|
||||
this.#providedFactories = new Map(
|
||||
@@ -88,15 +87,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
);
|
||||
this.#loadedDefaultFactories = new Map();
|
||||
this.#implementations = new Map();
|
||||
this.#dependencyGraph = DependencyGraph.fromIterable(
|
||||
Array.from(this.#providedFactories).map(
|
||||
([serviceId, serviceFactory]) => ({
|
||||
value: serviceId,
|
||||
provides: [serviceId],
|
||||
consumes: Object.values(serviceFactory.deps).map(d => d.id),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#resolveFactory(
|
||||
@@ -163,10 +153,17 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
}
|
||||
}
|
||||
|
||||
checkForCircularDeps(): void {
|
||||
const circularDependencies = Array.from(
|
||||
this.#dependencyGraph.detectCircularDependencies(),
|
||||
#checkForCircularDeps(): void {
|
||||
const graph = DependencyGraph.fromIterable(
|
||||
Array.from(this.#providedFactories).map(
|
||||
([serviceId, serviceFactory]) => ({
|
||||
value: serviceId,
|
||||
provides: [serviceId],
|
||||
consumes: Object.values(serviceFactory.deps).map(d => d.id),
|
||||
}),
|
||||
),
|
||||
);
|
||||
const circularDependencies = Array.from(graph.detectCircularDependencies());
|
||||
|
||||
if (circularDependencies.length) {
|
||||
const cycles = circularDependencies
|
||||
|
||||
@@ -20,10 +20,13 @@ import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { RootHttpRouterFactoryOptions } from '@backstage/backend-app-api';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { TokenManagerService } from '@backstage/backend-plugin-api';
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -113,6 +116,17 @@ export namespace mockServices {
|
||||
) => ServiceFactory<RootConfigService, 'root'>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootHttpRouter {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?: RootHttpRouterFactoryOptions | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootHttpRouterService> | undefined,
|
||||
) => ServiceMock<RootHttpRouterService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootLifecycle {
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<RootLifecycleService, 'root'>;
|
||||
@@ -169,6 +183,28 @@ export namespace mockServices {
|
||||
}
|
||||
}
|
||||
|
||||
// @public
|
||||
export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
static from<TService, TScope extends 'root' | 'plugin'>(
|
||||
subject:
|
||||
| ServiceFactory<TService, TScope>
|
||||
| (() => ServiceFactory<TService, TScope>),
|
||||
options?: ServiceFactoryTesterOptions,
|
||||
): ServiceFactoryTester<TService, TScope>;
|
||||
get(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService>;
|
||||
getService<TGetService, TGetScope extends 'root' | 'plugin'>(
|
||||
service: ServiceRef<TGetService, TGetScope>,
|
||||
...args: 'root' extends TGetScope ? [] : [pluginId?: string]
|
||||
): Promise<TGetService>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface ServiceFactoryTesterOptions {
|
||||
dependencies?: Array<ServiceFactory | (() => ServiceFactory)>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ServiceMock<TService> = {
|
||||
factory: ServiceFactory<TService>;
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
lifecycleServiceFactory,
|
||||
loggerServiceFactory,
|
||||
permissionsServiceFactory,
|
||||
rootHttpRouterServiceFactory,
|
||||
rootLifecycleServiceFactory,
|
||||
schedulerServiceFactory,
|
||||
urlReaderServiceFactory,
|
||||
@@ -184,6 +185,12 @@ export namespace mockServices {
|
||||
use: jest.fn(),
|
||||
}));
|
||||
}
|
||||
export namespace rootHttpRouter {
|
||||
export const factory = rootHttpRouterServiceFactory;
|
||||
export const mock = simpleMock(coreServices.rootHttpRouter, () => ({
|
||||
use: jest.fn(),
|
||||
}));
|
||||
}
|
||||
export namespace lifecycle {
|
||||
export const factory = lifecycleServiceFactory;
|
||||
export const mock = simpleMock(coreServices.lifecycle, () => ({
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactoryTester } from './ServiceFactoryTester';
|
||||
|
||||
const rootServiceRef = createServiceRef<string>({ id: 'a', scope: 'root' });
|
||||
const pluginServiceRef = createServiceRef<string>({ id: 'b', scope: 'plugin' });
|
||||
const sharedPluginServiceRef = createServiceRef<string>({
|
||||
id: 'c',
|
||||
scope: 'plugin',
|
||||
});
|
||||
|
||||
const rootFactory = createServiceFactory({
|
||||
service: rootServiceRef,
|
||||
deps: {},
|
||||
factory: async () => 'root',
|
||||
});
|
||||
|
||||
const pluginFactory = createServiceFactory({
|
||||
service: pluginServiceRef,
|
||||
deps: { plugin: coreServices.pluginMetadata },
|
||||
factory: async ({ plugin }) => `${plugin.getId()}-plugin`,
|
||||
});
|
||||
|
||||
const sharedPluginFactory = createServiceFactory({
|
||||
service: sharedPluginServiceRef,
|
||||
deps: { plugin: coreServices.pluginMetadata },
|
||||
createRootContext() {
|
||||
return { counter: 0 };
|
||||
},
|
||||
factory: async ({ plugin }, state) => {
|
||||
state.counter += 1;
|
||||
return `${plugin.getId()}-${state.counter}-plugin`;
|
||||
},
|
||||
});
|
||||
|
||||
describe('ServiceFactoryTester', () => {
|
||||
it('should test a root service factory', async () => {
|
||||
const tester = ServiceFactoryTester.from(rootFactory);
|
||||
|
||||
await expect(tester.get()).resolves.toBe('root');
|
||||
});
|
||||
|
||||
it('should test a plugin service factory', async () => {
|
||||
const tester = ServiceFactoryTester.from(pluginFactory);
|
||||
|
||||
await expect(tester.get('x')).resolves.toBe('x-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-plugin');
|
||||
await expect(tester.get('z')).resolves.toBe('z-plugin');
|
||||
});
|
||||
|
||||
it('should test a plugin service factory with root context', async () => {
|
||||
const tester = ServiceFactoryTester.from(sharedPluginFactory);
|
||||
|
||||
await expect(tester.get('x')).resolves.toBe('x-1-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin');
|
||||
await expect(tester.get('z')).resolves.toBe('z-3-plugin');
|
||||
|
||||
const tester2 = ServiceFactoryTester.from(sharedPluginFactory);
|
||||
|
||||
await expect(tester2.get('z')).resolves.toBe('z-1-plugin');
|
||||
await expect(tester2.get('y')).resolves.toBe('y-2-plugin');
|
||||
await expect(tester2.get('x')).resolves.toBe('x-3-plugin');
|
||||
await expect(tester2.get('x')).resolves.toBe('x-3-plugin');
|
||||
await expect(tester2.get('y')).resolves.toBe('y-2-plugin');
|
||||
await expect(tester2.get('z')).resolves.toBe('z-1-plugin');
|
||||
});
|
||||
|
||||
it('should use dependencies', async () => {
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<string>({ id: 'concat' }),
|
||||
deps: { root: rootServiceRef, plugin: pluginServiceRef },
|
||||
factory: async ({ root, plugin }) => `${root}, ${plugin}`,
|
||||
}),
|
||||
{ dependencies: [rootFactory, pluginFactory()] },
|
||||
);
|
||||
|
||||
await expect(tester.get('x')).resolves.toBe('root, x-plugin');
|
||||
});
|
||||
|
||||
it('should use dependencies with root context', async () => {
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<string>({ id: 'concat' }),
|
||||
deps: { shared: sharedPluginServiceRef, plugin: pluginServiceRef },
|
||||
factory: async ({ shared, plugin }) => `${shared}, ${plugin}`,
|
||||
}),
|
||||
{ dependencies: [sharedPluginFactory(), pluginFactory] },
|
||||
);
|
||||
|
||||
await expect(tester.get('x')).resolves.toBe('x-1-plugin, x-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin');
|
||||
await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin');
|
||||
await expect(tester.get('z')).resolves.toBe('z-3-plugin, z-plugin');
|
||||
});
|
||||
|
||||
it('should prioritize the subject implementation', async () => {
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: rootServiceRef,
|
||||
deps: {},
|
||||
factory: async () => 'other-root',
|
||||
}),
|
||||
{ dependencies: [rootFactory] },
|
||||
);
|
||||
|
||||
await expect(tester.get()).resolves.toBe('other-root');
|
||||
});
|
||||
|
||||
it('should throw on missing dependencies', async () => {
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: pluginServiceRef,
|
||||
deps: { root: rootServiceRef },
|
||||
factory: async () => 'plugin',
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(tester.get('x')).rejects.toThrow(
|
||||
"Failed to instantiate service 'b' for 'x' because the following dependent services are missing: 'a'",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ServiceFactory, ServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { defaultServiceFactories } from './TestBackend';
|
||||
// Direct internal import to avoid duplication.
|
||||
// This is a relative import in order to make sure that the implementation is duplicated
|
||||
// rather than leading to an import from @backstage/backend-app-api.
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { ServiceRegistry } from '../../../../backend-app-api/src/wiring/ServiceRegistry';
|
||||
|
||||
/**
|
||||
* Options for {@link ServiceFactoryTester}.
|
||||
* @public
|
||||
*/
|
||||
export interface ServiceFactoryTesterOptions {
|
||||
/**
|
||||
* Additional service factories to make available as dependencies.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If a service factory is provided for a service that already has a default
|
||||
* implementation, the provided factory will override the default.
|
||||
*/
|
||||
dependencies?: Array<ServiceFactory | (() => ServiceFactory)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility to help test service factories in isolation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
readonly #subject: ServiceRef<TService, TScope>;
|
||||
readonly #registry: ServiceRegistry;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ServiceFactoryTester} used to test the provided subject.
|
||||
*
|
||||
* @param subject - The service factory to test.
|
||||
* @param options - Additional options
|
||||
* @returns A new tester instance for the provided subject.
|
||||
*/
|
||||
static from<TService, TScope extends 'root' | 'plugin'>(
|
||||
subject:
|
||||
| ServiceFactory<TService, TScope>
|
||||
| (() => ServiceFactory<TService, TScope>),
|
||||
options?: ServiceFactoryTesterOptions,
|
||||
) {
|
||||
const subjectFactory = typeof subject === 'function' ? subject() : subject;
|
||||
const registry = ServiceRegistry.create([
|
||||
...defaultServiceFactories,
|
||||
...(options?.dependencies?.map(f =>
|
||||
typeof f === 'function' ? f() : f,
|
||||
) ?? []),
|
||||
subjectFactory,
|
||||
]);
|
||||
return new ServiceFactoryTester(subjectFactory.service, registry);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
subject: ServiceRef<TService, TScope>,
|
||||
registry: ServiceRegistry,
|
||||
) {
|
||||
this.#subject = subject;
|
||||
this.#registry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the service instance for the subject.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If the subject is a plugin scoped service factory a plugin ID
|
||||
* can be provided to instantiate the service for a specific plugin.
|
||||
*
|
||||
* By default the plugin ID 'test' is used.
|
||||
*/
|
||||
async get(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService> {
|
||||
const [pluginId] = args;
|
||||
return this.#registry.get(this.#subject, pluginId ?? 'test')!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service instance for any of the provided dependencies or built-in services.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* A plugin ID can optionally be provided for plugin scoped services, otherwise the plugin ID 'test' is used.
|
||||
*/
|
||||
async getService<TGetService, TGetScope extends 'root' | 'plugin'>(
|
||||
service: ServiceRef<TGetService, TGetScope>,
|
||||
...args: 'root' extends TGetScope ? [] : [pluginId?: string]
|
||||
): Promise<TGetService> {
|
||||
const [pluginId] = args;
|
||||
const instance = await this.#registry.get(service, pluginId ?? 'test');
|
||||
if (instance === undefined) {
|
||||
throw new Error(`Service '${service.id}' not found`);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ export interface TestBackend extends Backend {
|
||||
readonly server: ExtendedHttpServer;
|
||||
}
|
||||
|
||||
const defaultServiceFactories = [
|
||||
export const defaultServiceFactories = [
|
||||
mockServices.cache.factory(),
|
||||
mockServices.rootConfig.factory(),
|
||||
mockServices.database.factory(),
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
ServiceFactoryTester,
|
||||
type ServiceFactoryTesterOptions,
|
||||
} from './ServiceFactoryTester';
|
||||
export { startTestBackend } from './TestBackend';
|
||||
export type { TestBackend, TestBackendOptions } from './TestBackend';
|
||||
|
||||
Reference in New Issue
Block a user