({
+ id: 'y',
+});
+
+function Verifier() {
+ const holder = useApiHolder();
+ const x = holder.get(xApiRef);
+ const y = holder.get(yApiRef);
+
+ return (
+
+ {x ? (
+
+ x={x.a},{x.b}
+
+ ) : (
+ no x
+ )}
+ {y ? y={y} : no y}
+
+ );
+}
+
+describe('TestApiProvider', () => {
+ it('should provide APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('x=a,3')).toBeInTheDocument();
+ expect(screen.getByText('y=y')).toBeInTheDocument();
+ });
+
+ it('should provide partial APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('x=a,')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+
+ it('should require partial implementations to still match types', () => {
+ render(
+ // @ts-expect-error
+
+
+ ,
+ );
+ expect(screen.getByText('x=3,')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+
+ it('should allow empty APIs', () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText('no x')).toBeInTheDocument();
+ expect(screen.getByText('no y')).toBeInTheDocument();
+ });
+});
+
+describe('TestApiRegistry', () => {
+ it('should be created with APIs', () => {
+ const x = { a: 'a', b: 3 };
+ const y = 'y';
+ const registry = TestApiRegistry.from([xApiRef, x], [yApiRef, y]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBe(y);
+ });
+
+ it('should allow partial implementations', () => {
+ const x = { a: 'a' };
+ const registry = TestApiRegistry.from([xApiRef, x]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBeUndefined();
+ });
+
+ it('should require partial implementations to match types', () => {
+ const x = { a: 2 };
+ // @ts-expect-error
+ const registry = TestApiRegistry.from([xApiRef, x]);
+
+ expect(registry.get(xApiRef)).toBe(x);
+ expect(registry.get(yApiRef)).toBeUndefined();
+ });
+
+ it('should prefer last duplicate API that was provided', () => {
+ const x1 = { a: 'a' };
+ const x2 = { a: 's' };
+ const x3 = { a: 'd' };
+ const registry = TestApiRegistry.from(
+ [xApiRef, x1],
+ [xApiRef, x2],
+ [xApiRef, x3],
+ );
+
+ expect(registry.get(xApiRef)).toBe(x3);
+ });
+});
diff --git a/packages/test-utils/src/testUtils/TestApiProvider.tsx b/packages/test-utils/src/testUtils/TestApiProvider.tsx
new file mode 100644
index 0000000000..b1499b0cde
--- /dev/null
+++ b/packages/test-utils/src/testUtils/TestApiProvider.tsx
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 React, { ReactNode } from 'react';
+import { ApiProvider } from '@backstage/core-app-api';
+import { ApiRef, ApiHolder } from '@backstage/core-plugin-api';
+
+/** @ignore */
+type TestApiProviderPropsApiPair = TApi extends infer TImpl
+ ? readonly [ApiRef, Partial]
+ : never;
+
+/** @ignore */
+type TestApiProviderPropsApiPairs = {
+ [TIndex in keyof TApiPairs]: TestApiProviderPropsApiPair;
+};
+
+/**
+ * Properties for the {@link TestApiProvider} component.
+ *
+ * @public
+ */
+export type TestApiProviderProps = {
+ apis: readonly [...TestApiProviderPropsApiPairs];
+ children: ReactNode;
+};
+
+/**
+ * The `TestApiRegistry` is an {@link @backstage/core-plugin-api#ApiHolder} implementation
+ * that is particularly well suited for development and test environments such as
+ * unit tests, storybooks, and isolated plugin development setups.
+ *
+ * @public
+ */
+export class TestApiRegistry implements ApiHolder {
+ /**
+ * Creates a new {@link TestApiRegistry} with a list of API implementation pairs.
+ *
+ * Similar to the {@link TestApiProvider}, there is no need to provide a full
+ * implementation of each API, it's enough to implement the methods that are tested.
+ *
+ * @example
+ * ```ts
+ * const apis = TestApiRegistry.from(
+ * [configApiRef, new ConfigReader({})],
+ * [identityApiRef, { getUserId: () => 'tester' }],
+ * );
+ * ```
+ *
+ * @public
+ * @param apis - A list of pairs mapping an ApiRef to its respective implementation.
+ */
+ static from(
+ ...apis: readonly [...TestApiProviderPropsApiPairs]
+ ) {
+ return new TestApiRegistry(
+ new Map(apis.map(([api, impl]) => [api.id, impl])),
+ );
+ }
+
+ private constructor(private readonly apis: Map) {}
+
+ /**
+ * Returns an implementation of the API.
+ *
+ * @public
+ */
+ get(api: ApiRef): T | undefined {
+ return this.apis.get(api.id) as T | undefined;
+ }
+}
+
+/**
+ * The `TestApiProvider` is a Utility API context provider that is particularly
+ * well suited for development and test environments such as unit tests, storybooks,
+ * and isolated plugin development setups.
+ *
+ * It lets you provide any number of API implementations, without necessarily
+ * having to fully implement each of the APIs.
+ *
+ * A migration from `ApiRegistry` and `ApiProvider` might look like this, from:
+ *
+ * ```tsx
+ * renderInTestApp(
+ *
+ * {...}
+ *
+ * )
+ * ```
+ *
+ * To the following:
+ *
+ * ```tsx
+ * renderInTestApp(
+ *
+ * {...}
+ *
+ * )
+ * ```
+ *
+ * Note that the cast to `IdentityApi` is no longer needed as long as the mock API
+ * implements a subset of the `IdentityApi`.
+ *
+ * @public
+ **/
+export const TestApiProvider = ({
+ apis,
+ children,
+}: TestApiProviderProps) => {
+ return (
+
+ );
+};
diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx
index 7d93d606cc..c778c5c837 100644
--- a/packages/test-utils/src/testUtils/index.tsx
+++ b/packages/test-utils/src/testUtils/index.tsx
@@ -22,3 +22,5 @@ export * from './msw';
export * from './Keyboard';
export * from './logCollector';
export * from './testingLibrary';
+export { TestApiProvider, TestApiRegistry } from './TestApiProvider';
+export type { TestApiProviderProps } from './TestApiProvider';