Repository navigation
testing library async test isolation
title: "Fix Guide — Testing Library async test isolation / multiple elements found" type: fix-guide domain: frontend framework: react testing: "vitest + @testing-library/react" severity: error tags: [testing-library, vitest, async-tests, act, tanstack-router, test-isolation] created: 2026-09-11
Vitest reports a duplicate-element error in a test that follows a test using an async render helper:
Found multiple elements with the role "form" and name /create reporting unit/i
The failure occurs in ReportingUnitCreate/index.unit.test.tsx, test component initialization > sets up form with initial null values. The component renders one form with aria-label="Create reporting unit" and data-testid="create-ru-form".
The symptom is test contamination: a previous test left a rendered DOM tree in place because its async render helper was not awaited. When the next test queries by role/name, it finds both the leaked DOM and the fresh DOM, triggering the multiple-elements error.
renderComponent creates a TanStack test router and renders inside act(async () => { render(...) }). The preceding test initializes without errors called:
expect(() => renderComponent()).not.toThrow()This call is synchronous. renderComponent returns a Promise, but the assertion does not await it. The router load and render therefore settle asynchronously after the test completes, leaking the previous render into the shared DOM.
A secondary fragility is the query strategy. Role/name queries are ambiguous when leaked DOM exists. Using a unique test ID removes ambiguity.
Make the helper's call site asynchronous and await it before asserting.
// Before
expect(() => renderComponent()).not.toThrow()
// After
await renderComponent()Ensure every test that invokes a helper performing router loading, async act, or rendering awaits it before the next assertion or test boundary.
Prefer getByTestId for a component you control. Add a stable data-testid to the form:
// component
<form aria-label="Create reporting unit" data-testid="create-ru-form" onSubmit={handleSubmit}>Query the form by test ID:
const form = screen.getByTestId('create-ru-form')This makes the assertion resilient to name collisions and leaked DOM.
import { act, render, screen } from '@testing-library/react'
import { createTestRouter } from '@/config/tests/routerTestHelper'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
async function renderComponent() {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
const router = createTestRouter(() => <ReportingUnitCreate />)
await router.load()
await act(async () => {
render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
)
})
}
describe('ReportingUnitCreate', () => {
it('initializes without errors', async () => {
await renderComponent()
expect(screen.getByTestId('create-ru-form')).toBeTruthy()
})
it('sets up form with initial null values', async () => {
await renderComponent()
const form = screen.getByTestId('create-ru-form')
expect(form).toBeTruthy()
})
})- Await every helper that performs router loading, async
act, or rendering work before asserting or ending a test. - Treat Promise-returning render helpers as asynchronous in name, signature, and call sites.
- Keep cleanup and test isolation assumptions explicit when a helper mounts providers or routers.
- Prefer a unique, intentional test contract such as
data-testidwhen the assertion targets one known element and accessible-name matching can be ambiguous.
After the change:
-
npm run test:unit -- --run src/components/waste/ReportingUnits/ReportingUnitCreate/index.unit.test.tsx— 44/44 tests passed -
npm exec eslint -- --max-warnings=0 src/components/waste/ReportingUnits/ReportingUnitCreate/index.unit.test.tsx— passed -
npm exec tsc -- --noEmit— passed
No application source or tests outside the changed test file were modified.
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository