Merge branch 'master' into runtime-module-federation-enablement
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -399,7 +399,7 @@ const parent = createExtension({
|
||||
|
||||
// Create a child extension that attaches to the parent's input
|
||||
const child = createExtension({
|
||||
attachTo: page.inputs.children, // Direct reference to the input
|
||||
attachTo: parent.inputs.children, // Direct reference to the input
|
||||
output: [coreExtensionData.reactElement], // Outputs are verified against the parent input
|
||||
// other options...
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ You can also pass overrides to the features array, for more details, please read
|
||||
|
||||
### Using an async features loader
|
||||
|
||||
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-defaults.createappfeatureloader/) object and pass it to the `features` option:
|
||||
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createFrontendFeatureLoader.html) object and pass it to the `features` option:
|
||||
|
||||
```tsx title="packages/app/src/App.tsx"
|
||||
import { createApp } from '@backstage/frontend-defaults';
|
||||
|
||||
@@ -18,7 +18,7 @@ extensions:
|
||||
```
|
||||
|
||||
:::warning
|
||||
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
|
||||
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
|
||||
:::
|
||||
|
||||
## Override built-in extensions
|
||||
@@ -107,7 +107,7 @@ This is the extension that creates the app root element, so it renders root leve
|
||||
|
||||
##### Alert Display
|
||||
|
||||
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/docs/reference/core-plugin-api.alertapi).
|
||||
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html).
|
||||
|
||||
| kind | namespace | name | id |
|
||||
| :--------------: | :-------: | :-----------: | :----------------------------------: |
|
||||
@@ -125,12 +125,12 @@ An app root element extension that displays messages posted via the [`AlertApi`]
|
||||
If you do not want to display alerts, disable this extension or if the available settings do not meet your needs, override this extension.
|
||||
|
||||
:::warning
|
||||
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
|
||||
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
|
||||
:::
|
||||
|
||||
##### OAuth Request Dialog
|
||||
|
||||
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/docs/reference/core-plugin-api.oauthrequestapi/).
|
||||
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.OAuthRequestApi.html).
|
||||
|
||||
| kind | namespace | name | id |
|
||||
| :--------------: | :-------: | :------------------: | :-----------------------------------------: |
|
||||
@@ -146,10 +146,10 @@ Renders the app's sidebar and content in a specific layout.
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Name | Description | Type | Optional | Default | Extension creator |
|
||||
| ------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
|
||||
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Nav` extension. |
|
||||
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Routes` extension. |
|
||||
| Name | Description | Type | Optional | Default | Extension creator |
|
||||
| ------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
|
||||
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.coreExtensionData.html) | false | - | Override the `App/Nav` extension. |
|
||||
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.coreExtensionData.html) | false | - | Override the `App/Routes` extension. |
|
||||
|
||||
### App nav
|
||||
|
||||
@@ -161,10 +161,10 @@ Extension responsible for rendering the logo and items in the app's sidebar.
|
||||
|
||||
#### Inputs
|
||||
|
||||
| Name | Description | Type | Optional | Default | Extension creator |
|
||||
| ------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | true | - | [NavContentBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) |
|
||||
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
|
||||
| Name | Description | Type | Optional | Default | Extension creator |
|
||||
| ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) | true | - | [NavContentBlueprint](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) |
|
||||
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
|
||||
|
||||
### App routes
|
||||
|
||||
|
||||
@@ -31,50 +31,50 @@ describe('Entity details component', () => {
|
||||
});
|
||||
```
|
||||
|
||||
To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API:
|
||||
To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component, pass API overrides to `renderInTestApp` using the `apis` option. Mock helpers are available from `@backstage/frontend-test-utils` and plugin-specific test utilities. For a deeper look at the available mock APIs and how to create your own, see [Testing with Utility APIs](../utility-apis/05-testing.md).
|
||||
|
||||
```tsx
|
||||
import { screen } from '@testing-library/react';
|
||||
import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { EntityDetails } from './plugin';
|
||||
import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';
|
||||
import { identityApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import { MyEntitiesList } 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',
|
||||
describe('MyEntitiesList', () => {
|
||||
it('should render entities owned by the current user', async () => {
|
||||
await renderInTestApp(<MyEntitiesList />, {
|
||||
apis: [
|
||||
[
|
||||
identityApiRef,
|
||||
mockApis.identity({ userEntityRef: 'user:default/guest' }),
|
||||
],
|
||||
[
|
||||
catalogApiRef,
|
||||
catalogApiMock({
|
||||
entities: [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'my-component' },
|
||||
spec: { type: 'service', owner: 'user:default/guest' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[catalogApiRef, catalogApiMock]]}>
|
||||
<EntityDetails entityRef={entityRef} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await expect(
|
||||
screen.findByText('The entity "test" is owned by "tools"'),
|
||||
screen.findByText('my-component'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
This approach provides the API overrides at the app level, which is useful when testing components that depend on APIs deep in the component tree.
|
||||
|
||||
The `TestApiProvider` component is also available for standalone rendering scenarios where you're not using `renderInTestApp` or other test utilities. Context providers like `EntityProvider` from `@backstage/plugin-catalog-react` can also be used to provide a mocked entity context to the component.
|
||||
|
||||
## Testing extensions
|
||||
|
||||
@@ -102,7 +102,35 @@ 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).
|
||||
You can also provide API overrides directly to `createExtensionTester` using the `apis` option:
|
||||
|
||||
```tsx
|
||||
import { screen } from '@testing-library/react';
|
||||
import {
|
||||
createExtensionTester,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import { identityApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { indexPageExtension } from './plugin';
|
||||
|
||||
describe('Index page', () => {
|
||||
it('should render with a custom identity', async () => {
|
||||
await renderInTestApp(
|
||||
createExtensionTester(indexPageExtension, {
|
||||
apis: [
|
||||
[
|
||||
identityApiRef,
|
||||
mockApis.identity({ userEntityRef: 'user:default/guest' }),
|
||||
],
|
||||
],
|
||||
}).reactElement(),
|
||||
);
|
||||
|
||||
expect(screen.getByText('Index Page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -172,7 +200,82 @@ describe('Index page', () => {
|
||||
});
|
||||
```
|
||||
|
||||
That's all for testing features!
|
||||
## Testing entity extensions
|
||||
|
||||
The `createTestEntityPage` utility from `@backstage/plugin-catalog-react/testUtils` simplifies testing entity cards and content extensions. It creates a test page that mounts at `/`, provides an `EntityProvider` context, and picks up entity extensions through input redirects.
|
||||
|
||||
```tsx
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderTestApp } from '@backstage/frontend-test-utils';
|
||||
import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import { myEntityCard } from './plugin';
|
||||
|
||||
describe('MyEntityCard', () => {
|
||||
it('should render for Component entities', async () => {
|
||||
const entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'my-service' },
|
||||
spec: { type: 'service', owner: 'team-a' },
|
||||
};
|
||||
|
||||
renderTestApp({
|
||||
extensions: [createTestEntityPage({ entity }), myEntityCard],
|
||||
});
|
||||
|
||||
expect(await screen.findByText('My Card Title')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Entity content extensions can be tested the exact same way, just pass your content extension instead of a card. The test page also supports entity filters defined on the extensions, so you can test filter behavior by providing different entity kinds. If your extension depends on APIs you can pass mock implementation using the `apis` option `renderTestApp`, or you can pass the API extension directly alongside your content extension.
|
||||
|
||||
Extensions that use `EntityRefLinks` or `useRelatedEntities` may require additional API mocking using the `apis` option on `renderTestApp`.
|
||||
|
||||
## Mounting routes
|
||||
|
||||
If your component or extension uses `useRouteRef` to generate links to other routes, you need to mount those routes in the test environment. Both `renderInTestApp` and `renderTestApp` support the `mountedRoutes` option for this purpose.
|
||||
|
||||
For example, given a component that uses `useRouteRef` to create a link:
|
||||
|
||||
```tsx
|
||||
import { useRouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { detailsRouteRef } from './routes';
|
||||
|
||||
export const MyComponent = () => {
|
||||
const detailsLink = useRouteRef(detailsRouteRef);
|
||||
|
||||
return <a href={detailsLink()}>View details</a>;
|
||||
};
|
||||
```
|
||||
|
||||
You can test it by mounting the route ref to a path using the `mountedRoutes` option:
|
||||
|
||||
```tsx
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/frontend-test-utils';
|
||||
import { detailsRouteRef } from './routes';
|
||||
import { MyComponent } from './MyComponent';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('should render a link to the plugin page', async () => {
|
||||
await renderInTestApp(<MyComponent />, {
|
||||
mountedRoutes: {
|
||||
'/my-plugin/details': detailsRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await screen.findByText('View details')).toHaveAttribute(
|
||||
'href',
|
||||
'/my-plugin/details',
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Extension tree snapshots
|
||||
|
||||
The `snapshot()` method on `ExtensionTester` returns a tree-shaped representation of the resolved extension hierarchy, which is convenient to use with Jest's `toMatchInlineSnapshot()` for verifying extension structure in tests.
|
||||
|
||||
## Missing something?
|
||||
|
||||
|
||||
@@ -73,6 +73,31 @@ Avoid using `convertLegacyEntityCardExtension` from `@backstage/core-compat-api`
|
||||
|
||||
Creates entity content to be displayed on the entity pages of the catalog plugin. Exported as `EntityContentBlueprint`.
|
||||
|
||||
Supports optional params such as `group` and `icon` to:
|
||||
|
||||
- group: string | false — associates the content with a tab group on the entity page (for example "overview", "quality", "deployment", or any custom id). You can override or disable this per-installation via app-config using `app.extensions[...].config.group`, where `false` removes the grouping.
|
||||
- icon: string — sets the tab icon. Note: when providing a string, the icon is looked up via the app's IconsApi; make sure icon bundles are enabled/installed in your app (see the Icons blueprint reference above) so that the icon id you use is available.
|
||||
|
||||
To render icons in the entity page tabs, the page must also have icons enabled via app configuration. Set `showNavItemIcons: true` on the catalog entity page config (created via `page:catalog/entity`). Example:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
extensions:
|
||||
# Entity page
|
||||
- page:catalog/entity:
|
||||
config:
|
||||
# Enable tab- and group-icons
|
||||
showNavItemIcons: true
|
||||
# Optionally override default groups and their icons
|
||||
groups:
|
||||
- overview:
|
||||
title: Overview
|
||||
icon: dashboard
|
||||
- documentation:
|
||||
title: Docs
|
||||
icon: description
|
||||
```
|
||||
|
||||
Avoid using `convertLegacyEntityContentExtension` from `@backstage/core-compat-api` to convert legacy entity content extensions to the new system. Instead, use the `EntityContentBlueprint` directly. The legacy converter is only intended to help adapt 3rd party plugins that you don't control, and doesn't produce as good results as using the blueprint directly.
|
||||
|
||||
## Extension blueprints in `@backstage/plugin-search-react/alpha`
|
||||
|
||||
@@ -9,4 +9,4 @@ description: The Frontend System
|
||||
|
||||
We recommend migrating your frontend plugins to the new frontend system. If you do please do so under an `/alpha` sub-path export.
|
||||
|
||||
You can find an example app setup in the [`app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next).
|
||||
You can find an example app setup in the [`app` package](https://github.com/backstage/backstage/tree/master/packages/app).
|
||||
|
||||
@@ -35,6 +35,14 @@ Most utility APIs are usable directly without any configuration. But they are pr
|
||||
|
||||
These cases are all described in [the main article](./04-configuring.md).
|
||||
|
||||
## Testing with utility APIs
|
||||
|
||||
> For details, [see the main article](./05-testing.md).
|
||||
|
||||
When testing frontend components and extensions, you often need to provide mock implementations of the utility APIs they depend on. The `@backstage/frontend-test-utils` package provides the `mockApis` namespace with ready-made mocks for all core utility APIs, which can be passed to test utilities like `renderInTestApp` and `TestApiProvider`.
|
||||
|
||||
These are described in detail in [the main article](./05-testing.md).
|
||||
|
||||
## Migrating from the old frontend system
|
||||
|
||||
If you want to learn how to migrate your own utility APIs from the old frontend system to the new one, that's described in the [Migrating APIs guide](../building-plugins/05-migrating.md#migrating-apis).
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
id: testing
|
||||
title: Testing with Utility APIs
|
||||
sidebar_label: Testing
|
||||
description: Mocking and testing Utility APIs
|
||||
---
|
||||
|
||||
When testing frontend components and extensions, you often need to provide mock implementations of the utility APIs they depend on. The `@backstage/frontend-test-utils` package provides the `mockApis` namespace with ready-made mocks for all core utility APIs.
|
||||
|
||||
## The `mockApis` namespace
|
||||
|
||||
The `mockApis` namespace is the main entry point for creating mock utility API instances in tests. It provides two usage patterns for each API:
|
||||
|
||||
### Fake instances
|
||||
|
||||
Call the API function directly to create a fake instance with simplified but functional behavior. These are useful when your test needs the API to actually work, not just be stubbed.
|
||||
|
||||
```ts
|
||||
import { mockApis } from '@backstage/frontend-test-utils';
|
||||
|
||||
const configApi = mockApis.config({ data: { app: { title: 'Test' } } });
|
||||
configApi.getString('app.title'); // 'Test'
|
||||
|
||||
const alertApi = mockApis.alert();
|
||||
alertApi.post({ message: 'hello' });
|
||||
expect(alertApi.getAlerts()).toHaveLength(1);
|
||||
```
|
||||
|
||||
### Jest mocks
|
||||
|
||||
Call `.mock()` to get an instance where every method is a `jest.fn()`. You can optionally provide partial implementations. This is useful when you want to assert that specific methods were called.
|
||||
|
||||
```ts
|
||||
import { mockApis } from '@backstage/frontend-test-utils';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
|
||||
const permissionApi = mockApis.permission.mock({
|
||||
authorize: async () => ({ result: AuthorizeResult.ALLOW }),
|
||||
});
|
||||
|
||||
// ... exercise the component ...
|
||||
|
||||
expect(permissionApi.authorize).toHaveBeenCalledTimes(1);
|
||||
```
|
||||
|
||||
## Providing mock APIs in tests
|
||||
|
||||
### With `renderInTestApp`
|
||||
|
||||
```tsx
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';
|
||||
|
||||
await renderInTestApp(<MyComponent />, {
|
||||
apis: [
|
||||
mockApis.identity({ userEntityRef: 'user:default/guest' }),
|
||||
mockApis.config({ data: { app: { title: 'Test App' } } }),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
You can also use the `[apiRef, implementation]` tuple syntax to provide any API implementation, including ones that aren't from `mockApis`:
|
||||
|
||||
```tsx
|
||||
import { myCustomApiRef } from '../apis';
|
||||
|
||||
const myCustomApiInstance = {
|
||||
// ...
|
||||
};
|
||||
|
||||
await renderInTestApp(<MyComponent />, {
|
||||
apis: [
|
||||
mockApis.identity({ userEntityRef: 'user:default/guest' }),
|
||||
[myCustomApiRef, myCustomApiInstance],
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### With `renderTestApp`
|
||||
|
||||
The same `apis` option is available on `renderTestApp`, which is commonly used when testing extensions or entity pages:
|
||||
|
||||
```tsx
|
||||
import { renderTestApp, mockApis } from '@backstage/frontend-test-utils';
|
||||
import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';
|
||||
|
||||
renderTestApp({
|
||||
extensions: [createTestEntityPage({ entity }), myEntityCard],
|
||||
apis: [mockApis.permission()],
|
||||
});
|
||||
```
|
||||
|
||||
### With `TestApiProvider`
|
||||
|
||||
For standalone rendering scenarios where you're not using `renderInTestApp`, the `TestApiProvider` component accepts the same `apis` format:
|
||||
|
||||
```tsx
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestApiProvider, mockApis } from '@backstage/frontend-test-utils';
|
||||
|
||||
render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
mockApis.identity({ userEntityRef: 'user:default/guest' }),
|
||||
mockApis.alert(),
|
||||
]}
|
||||
>
|
||||
<MyComponent />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
```
|
||||
|
||||
## Plugin-specific test mocks
|
||||
|
||||
Plugins can provide their own mock APIs that follow the same pattern. For example, `@backstage/plugin-catalog-react` provides `catalogApiMock` in its `/testUtils` entry point:
|
||||
|
||||
```tsx
|
||||
import { renderTestApp } from '@backstage/frontend-test-utils';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
|
||||
renderTestApp({
|
||||
extensions: [myExtension],
|
||||
apis: [catalogApiMock({ entities: [entity] })],
|
||||
});
|
||||
```
|
||||
|
||||
### Creating your own mock APIs
|
||||
|
||||
If you maintain a plugin that exposes a utility API, you can provide mock utilities that follow the same function + `.mock()` pattern as the built-in `mockApis`.
|
||||
|
||||
Use `attachMockApiFactory` for fake instances with real behavior, and `createApiMock` for the jest-mocked `.mock()` variant where all methods are `jest.fn()`. The full pattern looks like this:
|
||||
|
||||
```ts
|
||||
import {
|
||||
attachMockApiFactory,
|
||||
createApiMock,
|
||||
type ApiMock,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
import { myApiRef, type MyApi } from '@internal/plugin-example-react';
|
||||
|
||||
// Fake instance with real behavior
|
||||
export function myApiMock(options?: { greeting?: string }) {
|
||||
return attachMockApiFactory(myApiRef, {
|
||||
greet: async () => options?.greeting ?? 'Hello!',
|
||||
});
|
||||
}
|
||||
|
||||
// Jest mock variant where all methods are jest.fn()
|
||||
export namespace myApiMock {
|
||||
export const mock = createApiMock(myApiRef, () => ({
|
||||
greet: jest.fn(),
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
Consumers can then use it just like the core mocks:
|
||||
|
||||
```tsx
|
||||
// Fake with real behavior
|
||||
await renderInTestApp(<MyComponent />, {
|
||||
apis: [myApiMock({ greeting: 'Hi there!' })],
|
||||
});
|
||||
|
||||
// Jest mock for assertions
|
||||
const api = myApiMock.mock({
|
||||
greet: async () => 'mocked',
|
||||
});
|
||||
|
||||
await renderInTestApp(<MyComponent />, {
|
||||
apis: [api],
|
||||
});
|
||||
|
||||
expect(api.greet).toHaveBeenCalledTimes(1);
|
||||
```
|
||||
|
||||
## Available mock APIs
|
||||
|
||||
The table below lists all core APIs available through the `mockApis` namespace.
|
||||
|
||||
| API | Fake instance | Notes |
|
||||
| --------------------------------- | --------------------- | ---------------------------------------------------------------------- |
|
||||
| `mockApis.alert()` | `MockAlertApi` | Collects alerts; has `getAlerts()`, `clearAlerts()`, `waitForAlert()` |
|
||||
| `mockApis.analytics()` | `MockAnalyticsApi` | Collects events; has `getEvents()` |
|
||||
| `mockApis.config({ data })` | `MockConfigApi` | Reads from a plain JSON object |
|
||||
| `mockApis.discovery({ baseUrl })` | Inline | Returns `${baseUrl}/api/${pluginId}`, defaults to `http://example.com` |
|
||||
| `mockApis.error(options?)` | `MockErrorApi` | Collects errors; has `getErrors()`, `waitForError()` |
|
||||
| `mockApis.featureFlags(options?)` | `MockFeatureFlagsApi` | In-memory flag state; has `getState()`, `setState()`, `clearState()` |
|
||||
| `mockApis.fetch(options?)` | `MockFetchApi` | Wraps native `fetch`; supports identity injection and plugin protocol |
|
||||
| `mockApis.identity(options?)` | Inline | Configurable user ref, ownership, token, profile |
|
||||
| `mockApis.permission(options?)` | `MockPermissionApi` | Defaults to `ALLOW`; accepts a handler function |
|
||||
| `mockApis.storage({ data })` | `MockStorageApi` | In-memory storage with bucket support |
|
||||
| `mockApis.translation()` | `MockTranslationApi` | Passthrough returning default messages from translation refs |
|
||||
|
||||
Each of these also has a `.mock()` variant that returns jest mocks, as described above.
|
||||
Reference in New Issue
Block a user