Verified against Claude Code · 2026-07-15
Write component tests that check behavior, not implementation details
Generates React Testing Library tests driven by accessible queries and user-visible behavior, with a guard against testing internal state or CSS classes.
The prompt
Ready to copy — highlighted parts are example details you can swap.
<role>
You are writing tests for a React component using React Testing Library and Vitest. You test what a user can see and do, not internal state or implementation details.
</role>
<component>
A SearchableList component with a text input that filters a list of items client-side, and a "Clear" button.
</component>
<key_interactions>
Typing in the search box filters the visible list to matching items; clicking Clear resets the input and shows the full list; typing something with no matches shows a "No results" message.
</key_interactions>
<testing_rules>
- Query elements the way a user would find them: getByRole, getByLabelText, getByText. Only fall back to a test-id when there's genuinely no accessible way to find the element, and say so in a comment when you do.
- Never query or assert on component internal state, prop values, or CSS class names directly — if a behavior is worth testing, it's worth testing through what renders or what happens on screen.
- Use userEvent (not fireEvent) for interactions, and always await it — userEvent's interactions are asynchronous, and a missing await is the single most common cause of a flaky test in this stack.
- Write one test per meaningful behavior from Typing in the search box filters the visible list to matching items; clicking Clear resets the input and shows the full list; typing something with no matches shows a "No results" message., named as a sentence describing the behavior ("shows an error when the field is left empty"), not as "test 1", "test 2".
- Where the component does something conditionally (shows X only when Y), write both the positive and the negative case — don't only test the happy path.
- If the component depends on network requests or a store, mock at that boundary, not by reaching into the component's internals.
</testing_rules>
<output_format>
A complete test file, imports included, using Vitest. Group related tests with describe blocks that name the feature being tested, not the component's file name.
</output_format>Customize the highlighted detailsoptional — the prompt above already works
Why this works
The query-by-role-first rule operationalizes Testing Library's actual guiding principle — tests should resemble how users interact with the app — and it doubles as an incidental accessibility check: if an element genuinely can't be found by getByRole or getByLabelText, that's frequently a real a11y gap the component should fix, not just a testing inconvenience to route around with a test-id. The mandatory await on userEvent targets a specific, well-documented bug class: Testing Library's userEvent v14+ methods return promises to accurately simulate real browser event timing, and a missing await is Testing Library's own most commonly cited cause of intermittent, hard-to-reproduce test failures. Requiring both the positive and negative case for every conditional behavior closes the gap where an LLM writes a technically-passing test suite that only ever exercises the happy path and never proves the "No results" message actually appears when it should.
What you get back
describe('filtering the list', () => { it('shows only matching items when typing in the search box', async () => { const user = userEvent.setup(); render(<SearchableList items={['Apple', 'Banana', 'Cherry']} />); await user.type(screen.getByRole('textbox', { name: /search/i }), 'ban'); expect(screen.getByText('Banana')).toBeInTheDocument(); expect(screen.queryByText('Apple')).not.toBeInTheDocument(); }); it('shows a "No results" message when nothing matches', async () => { const user = userEvent.setup(); render(<SearchableList items={['Apple', 'Banana']} />); await user.type(screen.getByRole('textbox', { name: /search/i }), 'zzz'); expect(screen.getByText(/no results/i)).toBeInTheDocument(); }); });
Verified against
Claude Code Sonnet 4.6 · 2026-07-15
Cursor 2.1 · 2026-07-26
Changelog
- 2026-07-15 — Initial publish, verified against Claude Code (Sonnet 4.6) and Cursor 2.1 using React Testing Library 16 and Vitest 3.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE

