diff --git a/.changeset/hungry-cherries-speak.md b/.changeset/hungry-cherries-speak.md new file mode 100644 index 0000000000..bd67f61446 --- /dev/null +++ b/.changeset/hungry-cherries-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Add support for installing backend features via module imports, for example `startTestBackend({ features: [import('my-plugin')] })`. diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 5831b093c5..43e671e440 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -246,7 +246,13 @@ export interface TestBackendOptions { }, ]; // (undocumented) - features?: Array BackendFeature)>; + features?: Array< + | BackendFeature + | (() => BackendFeature) + | Promise<{ + default: BackendFeature | (() => BackendFeature); + }> + >; } // @public diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 0b92ec3353..59b5906eb4 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -47,7 +47,11 @@ export interface TestBackendOptions { ]; }, ]; - features?: Array BackendFeature)>; + features?: Array< + | BackendFeature + | (() => BackendFeature) + | Promise<{ default: BackendFeature | (() => BackendFeature) }> + >; } /** @public */ @@ -78,11 +82,12 @@ export const defaultServiceFactories = [ ]; /** - * Given a set of extension points and plugins, find + * Given a set of extension points and features, find the extension + * points that we mock and tie them to the correct plugin ID. * @returns */ function createExtensionPointTestModules( - features: Array BackendFeature)>, + features: Array, extensionPointTuples?: readonly [ ref: ExtensionPoint, impl: unknown, @@ -92,12 +97,7 @@ function createExtensionPointTestModules( return []; } - const registrations = features.flatMap(featureOrFunction => { - const feature = - typeof featureOrFunction === 'function' - ? featureOrFunction() - : featureOrFunction; - + const registrations = features.flatMap(feature => { if (feature.$$type !== '@backstage/BackendFeature') { throw new Error( `Failed to add feature, invalid type '${feature.$$type}'`, @@ -172,13 +172,39 @@ function createExtensionPointTestModules( return modules; } +function isPromise(value: unknown | Promise): value is Promise { + return ( + typeof value === 'object' && + value !== null && + 'then' in value && + typeof value.then === 'function' + ); +} + +function unwrapFeature( + feature: BackendFeature | (() => BackendFeature), +): BackendFeature { + return typeof feature === 'function' ? feature() : feature; +} + const backendInstancesToCleanUp = new Array(); /** @public */ export async function startTestBackend( options: TestBackendOptions, ): Promise { - const { extensionPoints, features = [], ...otherOptions } = options; + const { extensionPoints, ...otherOptions } = options; + + // Unpack input into awaited plain BackendFeatures + const features: BackendFeature[] = await Promise.all( + options.features?.map(async val => { + if (isPromise(val)) { + const { default: feature } = await val; + return unwrapFeature(feature); + } + return unwrapFeature(val); + }) ?? [], + ); let server: ExtendedHttpServer;