Skip to main content

005 - Testing

The scaffolded plugin comes with tests already in place. Let's walk through how they work and how to extend them.

Unit tests​

Backstage frontend plugins use Jest as the test runner, React Testing Library for rendering components, and MSW (Mock Service Worker) for intercepting HTTP requests.

TodoList tests​

Open plugins/todo/src/components/TodoList/TodoList.test.tsx. The TodoList component is presentational, so testing it is straightforward — pass in data and verify it renders:

await renderInTestApp(<TodoList todos={todos} />);

expect(screen.getByText('First task')).toBeInTheDocument();

renderInTestApp from @backstage/frontend-test-utils sets up a minimal Backstage app context around the component, providing all the standard APIs that components might depend on.

TodoPage tests​

Open plugins/todo/src/components/TodoPage/TodoPage.test.tsx. The TodoPage component fetches data from the backend, so the tests use MSW to intercept HTTP requests and return test data:

const server = setupServer();
registerMswTestHooks(server);

it('renders todos from the backend', async () => {
server.use(
http.get('*/api/todo/todos', () =>
HttpResponse.json({
items: [{ id: '1', title: 'Mocked task' /* ... */ }],
}),
),
);

await renderInTestApp(<TodoPage />);

expect(await screen.findByText('Mocked task')).toBeInTheDocument();
});

A few things to note:

  • registerMswTestHooks sets up and tears down the MSW server around each test, so handlers do not leak between tests.
  • screen.findByText returns a promise that waits for the element to appear. This handles the asynchronous nature of data fetching without needing explicit waitFor calls.
  • The URL pattern */api/todo/todos matches regardless of the host, which keeps the test independent of the discovery API's resolved base URL.

The tests also cover the error case — when the backend returns a 500 status, the component falls back to rendering example todo items instead of displaying an error panel.

Running the tests​

From the repository root:

yarn test plugins/todo

Writing additional tests​

As you add features to your plugin, follow the same patterns:

  • For presentational components, pass props and assert on rendered output.
  • For components that fetch data, use MSW to mock the HTTP responses.
  • Prefer screen.findByText over waitFor for async assertions.
  • Test both the success and error paths.

Integration tests​

For end-to-end validation, you can use Playwright to test your plugin in a real browser against a running Backstage instance. Integration tests catch issues that unit tests cannot, such as routing problems, CSS regressions, or issues with the full API round-trip.

A basic Playwright test for the todo page might look like this:

import { test, expect } from '@playwright/test';

test('todo page shows the todo list', async ({ page }) => {
await page.goto('/todo');
await expect(page.getByText('Welcome to todo!')).toBeVisible();
await expect(page.getByRole('table')).toBeVisible();
});

Integration tests are most valuable for critical user flows. For most component-level validation, the unit testing approach described above provides faster feedback with less setup.