diff --git a/docs/frontend-system/building-plugins/testing.md b/docs/frontend-system/building-plugins/testing.md index c3315d2821..7fe719b1a3 100644 --- a/docs/frontend-system/building-plugins/testing.md +++ b/docs/frontend-system/building-plugins/testing.md @@ -14,7 +14,7 @@ description: Testing plugins in the frontend system Utilities for testing frontend features and components are available in `@backstage/frontend-test-utils`. -## Testing components +## Testing React components A component can be used for more than one extension, and it should be tested independently of an extension environment. @@ -28,7 +28,7 @@ import { EntityDetails } from './plugin'; describe('Entity details component', () => { it('should render the entity name and owner', async () => { - renderInTestApp(); + await renderInTestApp(); await expect( screen.getByText('The entity "test" is owned by "tools"'), @@ -37,7 +37,7 @@ describe('Entity details component', () => { }); ``` -It's important to highlight that mocking APIs for components is different from mocking them for extensions. In the snippet below, we wrapped the component within a `TestApiProvider` for mocking the Catalog API: +To mock [Utility APIs](../utility-apis/01-index.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: ```tsx import React from 'react'; @@ -52,14 +52,15 @@ import { EntityDetails } from './plugin'; describe('Entity details component', () => { it('should render the entity name and owner', async () => { - const catalogApiMock: Partial = { - getEntityFacets: () => - Promise.resolve({ + const catalogApiMock = { + async getEntityFacets() { + return { facets: { 'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }], }, - }), - }; + }, + } + } satisfies Partial; const entityRef = stringifyEntityRef({ kind: 'Component', @@ -67,7 +68,7 @@ describe('Entity details component', () => { name: 'test', }); - renderInTestApp( + await renderInTestApp( , @@ -80,9 +81,9 @@ describe('Entity details component', () => { }); ``` -## Testing features +## Testing extensions -To facilitate testing of frontend features, 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. +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. A number of features (frontend extensions and overrides) are also accepted by the tester. Here are some examples of how these facilities can be useful: @@ -97,9 +98,7 @@ import { indexPageExtension } from './plugin'; describe('Index page', () => { it('should render a the index page', () => { - const tester = createExtensionTester(indexPageExtension); - - tester.render(); + createExtensionTester(indexPageExtension).render(); expect(screen.getByText('Index Page')).toBeInTheDocument(); }); @@ -118,17 +117,18 @@ import { indexPageExtension, detailsPageExtension } from './plugin'; describe('Index page', async () => { it('should link to the details page', () => { - const tester = createExtensionTester(indexPageExtension); + createExtensionTester(indexPageExtension) + // Adding more extensions to the preset being tested + .add(detailsPageExtension) + .render(); - // Adding more extensions to the preset being tested - tester.add(detailsPageExtension); - tester.render(); + await expect(screen.findByText('Index Page')).toBeInTheDocument(); - expect(screen.getByText('Index Page')).toBeInTheDocument(); + await userEvent.click(screen.getByRole('link', { name: 'See details' })); - userEvent.click(screen.getByRole('link', { name: 'See details' })); - - await expect(screen.getByText('Details Page')).resolves.toBeInTheDocument(); + await expect( + screen.findByText('Details Page'), + ).resolves.toBeInTheDocument(); }); }); ``` @@ -154,7 +154,7 @@ import { import { indexPageExtension } from './plugin'; describe('Index page', () => { - it('should capture click events in anylitics', async () => { + it('should capture click events in analytics', async () => { // Mocking the analytics api implementation const analyticsApiMock = new MockAnalyticsApi(); @@ -165,14 +165,14 @@ describe('Index page', () => { }), }); - const tester = createExtensionTester(indexPageExtension); + createExtensionTester(indexPageExtension) + // Overriding the analytics api extension + .add(analyticsApiOverride) + .render(); - // Overriding the analytics api extension - tester.add(analyticsApiOverride); - - tester.render(); - - userEvent.click(await screen.findByRole('link', { name: 'See details' })); + await userEvent.click( + await screen.findByRole('link', { name: 'See details' }), + ); expect(analyticsApiMock.getEvents()[0]).toMatchObject({ action: 'click', @@ -194,24 +194,22 @@ import { indexPageExtension, detailsPageExtension } from './plugin'; describe('Index page', () => { it('should accepts a custom title via config', async () => { - const tester = createExtensionTester(indexPageExtension, { + createExtensionTester(indexPageExtension, { // Configuration specific of index page config: { title: 'Custom index' }, - }); - - tester.add(detailsExtensionPage, { - // Configuration specific of details page - config: { title: 'Custom details' }, - }); - - tester.render({ - // Configuration specific of the instance - config: { - app: { - title: 'Custom app', + }) + .add(detailsExtensionPage, { + // Configuration specific of details page + config: { title: 'Custom details' }, + }) + .render({ + // Configuration specific of the instance + config: { + app: { + title: 'Custom app', + }, }, - }, - }); + }); await expect( screen.findByRole('heading', { name: 'Custom app' }), @@ -221,7 +219,7 @@ describe('Index page', () => { screen.findByRole('heading', { name: 'Custom index' }), ).resolves.toBeInTheDocument(); - userEvent.click(screen.getByRole('link', { name: 'See details' })); + await userEvent.click(screen.getByRole('link', { name: 'See details' })); await expect( screen.findByText('Custom details'),