frontend-test-utils: add mountedRoutes option for renderTestApp + document

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-03 22:52:51 +01:00
parent 55ebaab48e
commit 013ec22eab
4 changed files with 100 additions and 0 deletions
@@ -0,0 +1,14 @@
---
'@backstage/frontend-test-utils': patch
---
Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`:
```typescript
renderTestApp({
extensions: [...],
mountedRoutes: {
'/my-path': myRouteRef,
},
});
```
@@ -202,6 +202,47 @@ describe('Index page', () => {
That's all for testing features!
## 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',
);
});
});
```
## Missing something?
If there's anything else you think needs to be covered in the docs or that you think isn't covered by the test utilities, please create an issue in the Backstage repository. You are always welcome to contribute as well!
@@ -136,6 +136,9 @@ export type RenderTestAppOptions<TApiPairs extends any[] = any[]> = {
extensions?: ExtensionDefinition<any>[];
features?: FrontendFeature[];
initialRouteEntries?: string[];
mountedRoutes?: {
[path: string]: RouteRef;
};
apis?: readonly [...TestApiPairs<TApiPairs>];
};
@@ -14,14 +14,17 @@
* limitations under the License.
*/
import { Fragment } from 'react';
import { createSpecializedApp } from '@backstage/frontend-app-api';
import {
coreExtensionData,
createApiFactory,
createExtension,
createFrontendModule,
createFrontendPlugin,
ExtensionDefinition,
FrontendFeature,
RouteRef,
} from '@backstage/frontend-plugin-api';
import { render } from '@testing-library/react';
import appPlugin from '@backstage/plugin-app';
@@ -63,6 +66,23 @@ export type RenderTestAppOptions<TApiPairs extends any[] = any[]> = {
*/
initialRouteEntries?: string[];
/**
* An object of paths to mount route refs on, with the key being the path and
* the value being the RouteRef that the path will be bound to. This allows
* the route refs to be used by `useRouteRef` in the rendered elements.
*
* @example
* ```ts
* renderTestApp({
* mountedRoutes: {
* '/my-path': myRouteRef,
* },
* extensions: [...],
* })
* ```
*/
mountedRoutes?: { [path: string]: RouteRef };
/**
* API overrides to provide to the test app. Use `mockApis` helpers
* from `@backstage/frontend-test-utils` to create mock implementations.
@@ -100,6 +120,28 @@ export function renderTestApp<TApiPairs extends any[] = any[]>(
) {
const extensions = [...(options.extensions ?? [])];
if (options.mountedRoutes) {
for (const [path, routeRef] of Object.entries(options.mountedRoutes)) {
extensions.push(
createExtension({
kind: 'test-route',
name: path,
attachTo: { id: 'app/routes', input: 'routes' },
output: [
coreExtensionData.reactElement,
coreExtensionData.routePath,
coreExtensionData.routeRef,
],
factory: () => [
coreExtensionData.reactElement(<Fragment />),
coreExtensionData.routePath(path),
coreExtensionData.routeRef(routeRef),
],
}),
);
}
}
const features: FrontendFeature[] = [
createFrontendModule({
pluginId: 'app',