Prerequisite: Review and apply the common guidelines in
common.mdbefore using this checklist.
Urgency: suggestion
Style
Flag only when the test is long enough or complex enough that structure affects readability (for example, multi-step setup/interactions or >10 lines). For short, self-evident tests, prefer a suggestion or no comment.
- Do not flag short tests (roughly 3-5 lines) where Arrange/Act/Assert is obvious without comments.
- Do not require AAA comments in parameterized tests when added comments make the test harder to scan than the code itself.
- Prefer suggestions over high-severity findings unless the repository explicitly enforces AAA comment structure in tests.
- Each section must be preceded by a comment —
// Arrange,// Act, and// Assert. - The Assert comment must include a brief explanation of how the described scenario is being verified. The explanation should summarize what the assertions prove about the behavior stated in the test name.
- Good:
// Assert - textarea shows hash subjects, participantId query param is ignored - Good:
// Assert - ID Search button is active - Bad:
// Assert(missing explanation) - Bad:
// Assert - check results(too vague; does not connect to the scenario)
- Good:
- Arrange may be omitted when there is no setup beyond what
beforeEachalready provides, but Act and Assert are always required. - Combined
// Act & Assertis acceptable only when the assertion must be wrapped inside awaitForthat is inseparable from the action (e.g., awaiting an async side-effect). In that case the comment must still include an explanation:- Good:
// Act & Assert - empty state message is shown and error was logged to console - Bad:
// Act & Assert
- Good:
- Multiple Act/Assert cycles in one test are allowed for stateful interaction flows (e.g., click then verify, click again then verify). Each cycle must carry its own
// Actand// Assert - ...comments.
Urgency: urgent
Maintainability
Flag when the symbol is clearly unused in the file or when a mock setup is not consumed by behavior under test. If a mock or import may be used implicitly (module mocking side effects, global setup conventions), verify before commenting.
- Do not flag side-effect imports (for example, polyfills or test environment setup) just because no identifier is referenced.
- Do not flag module-level
jest.mock(...)declarations that intentionally replace imports used elsewhere in the file. - If a placeholder parameter is required for function signature/position, allow underscore-prefixed names (for example,
_arg).
- Unused imports must be removed. If a symbol is imported but never referenced in the file, delete the import. This includes named imports, default imports, and type-only imports.
- Unused variables and constants must be removed. If a
const,let, or destructured binding is declared but never read, delete it. This applies to top-level declarations, insidedescribe/beforeEach/testblocks, and helper functions. - Unused mock declarations must be removed. If
jest.fn(),jest.mock(),jest.spyOn(), or a manual mock variable is set up but never referenced in an assertion or as a dependency, delete it. Mocks that are called implicitly (e.g., module-leveljest.mock('...')that replaces an import used elsewhere) are considered used. - Unused mock return values must be removed. If a mock is configured with
.mockReturnValue(),.mockResolvedValue(), or.mockImplementation()but the return value is never consumed or asserted on, simplify or remove the configuration. - Unused helper functions and factory functions must be removed. If a test utility, builder, or factory function defined in the file is never called, delete it.
- Unused parameters in callbacks must be prefixed with
_. If a callback parameter (e.g., in.mockImplementation((unusedArg) => ...)) is required for positional reasons but not used, prefix it with_to signal intent (e.g.,_unusedArg). - Unused
renderresults must not be destructured. Ifrender(<Component />)is called and the return value is not used, do not destructure it. Writerender(<Component />);instead ofconst { container } = render(<Component />);whencontaineris never referenced.
// ---- Unused imports ----
// ❌ BAD — ApiResponse is imported but never used
import { render, screen } from '@testing-library/react';
import { ApiResponse, UserProfile } from '../models';
const mockProfile: UserProfile = { name: 'Test' };
// ✅ GOOD — only referenced imports remain
import { render, screen } from '@testing-library/react';
import { UserProfile } from '../models';
const mockProfile: UserProfile = { name: 'Test' };
// ---- Unused variables ----
// ❌ BAD — mockHandler is declared but never used
const mockHandler = jest.fn();
const mockCallback = jest.fn();
test('calls callback on click', () => {
render(<Button onClick={mockCallback} />);
fireEvent.click(screen.getByRole('button'));
expect(mockCallback).toHaveBeenCalledTimes(1);
});
// ✅ GOOD — only mockCallback remains
const mockCallback = jest.fn();
test('calls callback on click', () => {
render(<Button onClick={mockCallback} />);
fireEvent.click(screen.getByRole('button'));
expect(mockCallback).toHaveBeenCalledTimes(1);
});
// ---- Unused mock setup ----
// ❌ BAD — jest.spyOn for console.warn is set up but never asserted or needed
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
test('renders error state', () => {
render(<ErrorBanner message="fail" />);
expect(screen.getByText('fail')).toBeInTheDocument();
expect(console.error).toHaveBeenCalled();
// console.warn is never checked
});
// ✅ GOOD — only the console.error spy remains
jest.spyOn(console, 'error').mockImplementation(() => {});
test('renders error state', () => {
render(<ErrorBanner message="fail" />);
expect(screen.getByText('fail')).toBeInTheDocument();
expect(console.error).toHaveBeenCalled();
});
// ---- Unused render destructuring ----
// ❌ BAD — container is destructured but never referenced
const { container } = render(<Header title="Hello" />);
expect(screen.getByText('Hello')).toBeInTheDocument();
// ✅ GOOD — render called without unused destructuring
render(<Header title="Hello" />);
expect(screen.getByText('Hello')).toBeInTheDocument();
// ---- Unused callback parameter ----
// ❌ BAD — `req` is not used but has no underscore prefix
mockFetch.mockImplementation((req, options) => {
return Promise.resolve({ ok: true });
});
// ✅ GOOD — unused positional parameter prefixed with _
mockFetch.mockImplementation((_req, options) => {
return Promise.resolve({ ok: true });
});Urgency: urgent
Correctness
Flag as a high-severity finding when the changed test triggers async React updates and asserts before the UI settles, or when act(...) warnings are present in test output. If the interaction and state updates are fully synchronous, do not force async patterns.
- Do not require
awaitfor purely synchronous interactions that do not trigger async state updates. - If the test uses a project helper that already waits for async UI stabilization, avoid duplicating
waitFor/findBycalls. - When fake timers are used, accept explicit
act(...)+ timer advancement patterns that correctly flush updates.
- If a component triggers async state updates after render, the test must await a UI-stable condition before assertions. Look for
useEffectwith async work, fetch-on-mount patterns, or promise-based state transitions. - User interactions that trigger async updates must be awaited.
userEvent.click,userEvent.type, and similar calls should beawaited when the resulting handler performs async work. - Tests with async UI behavior must be marked
async. A test function that usesawaitmust be declaredasync; a test whose component performs async work almost certainly needs both. - Presence of
act(...)warnings in test output is a defect, even when tests pass. Treat these warnings as test failures during review.
render(...)followed by immediate assertions while the component hasuseEffect(() => { fetch(...).then(...) }, [])or similar async-on-mount logic.userEvent.click/type/...followed by immediate assertions that depend on async updates.- Presence of
console.erroract(...)warnings in test output, even when tests pass. - Test functions not marked
asyncdespite the component performing async work.
await screen.findBy...(...)— preferred. Use for elements that appear after async work. Simpler and more idiomatic thanwaitFor.await waitFor(() => expect(...))— use when asserting on state-dependent conditions thatfindBycan't express (e.g., element attribute changes, disappearance).- Shared helpers like
waitForComponentToLoad()— call after render when available. await userEvent.click(...)— always await interactions that trigger async handlers.
// ❌ BAD — immediate assertion after render; component fetches data on mount
render(<ParticipantReports />);
expect(screen.getByLabelText(/enter animal ids/i)).toBeInTheDocument();
// ✅ GOOD — waits for async mount to settle before asserting
render(<ParticipantReports />);
await waitFor(() => {
expect(screen.getByText('General')).toBeVisible();
});
expect(screen.getByLabelText(/enter animal ids/i)).toBeInTheDocument();