feat(frontend-test-utils): add API override support to test utilities

Added support for API overrides in `createExtensionTester` and
`renderInTestApp` to allow tests to override specific APIs without
requiring wrapper components. This provides app-level API overrides
that are available throughout the entire extension tree.

The `apis` option follows the same typing pattern as `TestApiProvider`
from `@backstage/test-utils` for consistency and type safety.

Example usage:

```typescript
const tester = createExtensionTester(MyExtension, {
  apis: [
    [errorApiRef, mockErrorApi],
    [analyticsApiRef, mockAnalyticsApi],
  ],
});

renderInTestApp(<MyComponent />, {
  apis: [
    [errorApiRef, mockErrorApi],
    [analyticsApiRef, mockAnalyticsApi],
  ],
});
```

This enables cleaner tests with app-level API overrides, eliminating
the need to wrap components with TestApiProvider in many cases.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-01-31 15:10:34 +01:00
parent f2fc1def80
commit 22864b75a9
10 changed files with 440 additions and 25 deletions
@@ -76,6 +76,45 @@ describe('Entity details component', () => {
This pattern also works for many other context providers. An important example is the `EntityProvider` from the `@backstage/plugin-catalog-react` package, which you can use to provide a mocked entity context to the component.
Alternatively, you can pass API overrides directly to `renderInTestApp` using the `apis` option:
```tsx
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/frontend-test-utils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { EntityDetails } from './plugin';
describe('Entity details component', () => {
it('should render the entity name and owner', async () => {
const catalogApiMock = {
async getEntityFacets() {
return {
facets: {
'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }],
},
},
}
} satisfies Partial<typeof catalogApiRef.T>;
const entityRef = stringifyEntityRef({
kind: 'Component',
namespace: 'default',
name: 'test',
});
await renderInTestApp(<EntityDetails entityRef={entityRef} />, {
apis: [[catalogApiRef, catalogApiMock]],
});
await expect(
screen.findByText('The entity "test" is owned by "tools"'),
).resolves.toBeInTheDocument();
});
});
```
This approach provides the API overrides at the app level, which is useful when testing extensions that depend on APIs deep in the component tree.
## Testing extensions
To facilitate testing of frontend extensions, the `@backstage/frontend-test-utils` package provides a tester class which starts up an entire frontend harness, complete with a number of default features. You can then provide overrides for extensions whose behavior you need to adjust for the test run.
@@ -102,7 +141,29 @@ describe('Index page', () => {
});
```
This pattern also allows you to wrap the extension with context providers, such as the `TestApiProvider` that was introduced [above](#testing-react-components).
This pattern also allows you to wrap the extension with context providers, such as the `TestApiProvider` that was introduced [above](#testing-react-components). Alternatively, you can provide API overrides directly to `createExtensionTester`:
```tsx
import { screen } from '@testing-library/react';
import { createExtensionTester } from '@backstage/frontend-test-utils';
import { analyticsApiRef } from '@backstage/frontend-plugin-api';
import { indexPageExtension } from './plugin';
describe('Index page', () => {
it('should render and track analytics', async () => {
const analyticsApiMock = { captureEvent: jest.fn() };
await renderInTestApp(
createExtensionTester(indexPageExtension, {
apis: [[analyticsApiRef, analyticsApiMock]],
}).reactElement(),
);
expect(screen.getByText('Index Page')).toBeInTheDocument();
expect(analyticsApiMock.captureEvent).toHaveBeenCalled();
});
});
```
Note that the `.reactElement()` method will look for the `coreExtensionData.reactElement` data in the extension outputs. If that doesn't exist and the extension outputs something else that you want to test, you can access the output data using the `.get(dataRef)` method instead.