Type-safe API context provider for tests with flexible API registration and registry management. The TestApiProvider system enables precise control over which APIs are available to components during testing.
React component that provides API context for testing with type-safe API registration.
/**
* API context provider for tests with type-safe API registration
* @param props - Provider properties with APIs and children
* @returns JSX element providing API context to children
*/
function TestApiProvider<T extends readonly ApiPair[]>(
props: TestApiProviderProps<T>
): JSX.Element;
interface TestApiProviderProps<TApiPairs extends readonly ApiPair[]> {
/** Array of API pairs to register in the test context */
apis: TApiPairs;
/** Child components that will have access to the provided APIs */
children: ReactNode;
}
type ApiPair = readonly [ApiRef<any>, any];Usage Examples:
import {
TestApiProvider,
mockApis,
configApiRef,
identityApiRef,
storageApiRef
} from "@backstage/test-utils";
// Basic usage with mock APIs
function TestWrapper({ children }) {
return (
<TestApiProvider
apis={[
[configApiRef, mockApis.config({ data: { app: { title: 'Test' } } })],
[identityApiRef, mockApis.identity({ userEntityRef: 'user:default/test' })],
[storageApiRef, mockApis.storage()]
]}
>
{children}
</TestApiProvider>
);
}
// With custom implementations
<TestApiProvider
apis={[
[configApiRef, new CustomConfigApi()],
[errorApiRef, new MockErrorApi({ collect: true })]
]}
>
<MyComponent />
</TestApiProvider>
// Type-safe API pairs ensure correctness
<TestApiProvider
apis={[
[configApiRef, mockApis.config()], // ✓ Correct type
[configApiRef, mockApis.identity()] // ✗ Type error - wrong API type
]}
>
<MyComponent />
</TestApiProvider>ApiHolder implementation for tests that manages API registration and retrieval.
/**
* ApiHolder implementation for tests with type-safe API registration
*/
class TestApiRegistry implements ApiHolder {
/**
* Creates TestApiRegistry from API pairs with type safety
* @param apis - Variable number of API pairs to register
* @returns TestApiRegistry instance with registered APIs
*/
static from<TApiPairs extends readonly ApiPair[]>(...apis: TApiPairs): TestApiRegistry;
/**
* Retrieves API implementation by reference
* @param api - API reference to lookup
* @returns API implementation or undefined if not registered
*/
get<T>(api: ApiRef<T>): T | undefined;
}Usage Examples:
import { TestApiRegistry, mockApis } from "@backstage/test-utils";
// Create registry with APIs
const apiRegistry = TestApiRegistry.from(
[configApiRef, mockApis.config({ data: { backend: { baseUrl: 'http://localhost' } } })],
[identityApiRef, mockApis.identity()],
[discoveryApiRef, mockApis.discovery({ baseUrl: 'http://localhost:7007' })]
);
// Retrieve APIs
const configApi = apiRegistry.get(configApiRef);
const identityApi = apiRegistry.get(identityApiRef);
// Use with ApiProvider
<ApiProvider apis={apiRegistry}>
<MyComponent />
</ApiProvider>
// Custom registry usage
class CustomTestRegistry extends TestApiRegistry {
constructor() {
super();
// Custom initialization logic
}
}Core types for API registration and management in the test context.
/**
* Reference to an API with type information
*/
interface ApiRef<T> {
readonly id: string;
readonly T: T;
}
/**
* Interface for API holder implementations
*/
interface ApiHolder {
get<T>(api: ApiRef<T>): T | undefined;
}
/**
* Pair of API reference and its implementation
*/
type ApiPair = readonly [ApiRef<any>, any];
/**
* Extract API reference type from API pair
*/
type ApiRefFromPair<TPair> = TPair extends readonly [ApiRef<infer T>, any] ? ApiRef<T> : never;
/**
* Extract implementation type from API pair
*/
type ImplFromPair<TPair> = TPair extends readonly [ApiRef<any>, infer T] ? T : never;import { TestApiProvider, mockApis } from "@backstage/test-utils";
// Development-like configuration
const developmentApis = [
[configApiRef, mockApis.config({
data: {
app: { title: 'Dev App' },
backend: { baseUrl: 'http://localhost:7007' }
}
})],
[identityApiRef, mockApis.identity({
userEntityRef: 'user:default/developer'
})],
[permissionApiRef, mockApis.permission({ allow: true })]
] as const;
// Production-like configuration
const productionApis = [
[configApiRef, mockApis.config({
data: {
app: { title: 'Production App' },
backend: { baseUrl: 'https://api.company.com' }
}
})],
[identityApiRef, mockApis.identity({
userEntityRef: 'user:default/user123'
})],
[permissionApiRef, mockApis.permission({ allow: false })]
] as const;
// Use in tests
describe('MyComponent', () => {
test('works in development', () => {
render(
<TestApiProvider apis={developmentApis}>
<MyComponent />
</TestApiProvider>
);
});
test('works in production', () => {
render(
<TestApiProvider apis={productionApis}>
<MyComponent />
</TestApiProvider>
);
});
});import { TestApiProvider } from "@backstage/test-utils";
// Custom config API for specific test scenarios
class TestSpecificConfigApi implements ConfigApi {
constructor(private data: any) {}
getString(key: string): string {
// Custom logic for test scenario
if (key === 'app.title') return 'Test Specific Title';
return this.data[key] || '';
}
// Implement other ConfigApi methods...
}
// Use custom implementation
<TestApiProvider
apis={[
[configApiRef, new TestSpecificConfigApi({ feature: 'enabled' })],
[identityApiRef, mockApis.identity()]
]}
>
<ComponentUnderTest />
</TestApiProvider>// Layer API providers for complex scenarios
<TestApiProvider
apis={[
[configApiRef, mockApis.config()],
[identityApiRef, mockApis.identity()]
]}
>
<PluginContext>
<TestApiProvider
apis={[
[storageApiRef, mockApis.storage({ data: { theme: 'dark' } })]
]}
>
<MyComponent />
</TestApiProvider>
</PluginContext>
</TestApiProvider>type ReactNode = React.ReactNode;
interface JSX {
Element: React.ReactElement<any, any>;
}
/**
* Tuple type for API reference and implementation pairs
*/
type ApiPair = readonly [ApiRef<any>, any];
/**
* Extract tuple of API pairs from provider props
*/
type ExtractApiPairs<T> = T extends TestApiProviderProps<infer U> ? U : never;