Verdict
No
No, do not write a test for each prop of a component: props that only set content or style need none, because TypeScript checks them and the author sees them on screen, and each prop that changes behaviour, such as a callback or a disabled flag, gets one interaction test.
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilityimmediately
- Reversibilitytrivial
- Test costmoderate
Do not write a test for each prop; test the props that change behaviour. The typical case is a presentational React component in a TypeScript app, such as a profile card with name, avatarUrl and variant, which its author opens in Storybook after each change. Blast radius is users and Change frequency is regularly, about once a month for product components. Detectability is immediately and Reversibility is trivial: TypeScript rejects a missing prop, the author sees a display prop on screen, and a revert leaves nothing behind. Test cost is moderate, because tests for eight props take about an hour and break on markup edits, so rule R8 gives Do not test. For a callback prop, Detectability moves to same-day and the decision becomes Test minimally.
| When | Decision | Why |
|---|---|---|
| The prop is a callback, such as `onSave`, that the component calls on a click | Test minimally: one user-event test that asserts the arguments the callback receives | Detectability moves to same-day, because a dead button looks unchanged and a customer reports it |
| The props feed data that the app saves, such as `initialValues` of a form | Test: assert the submitted payload for the main path and each optional field | Detectability moves to eventually and Reversibility to with-effort, because a dropped value shows nothing and saved records need a repair script |
| A layout component forwards two props of one type, such as `startDate` and `endDate`, to a date picker | Test minimally: pass a distinct date to each prop and check which field shows each | Detectability moves to eventually, because a swapped prop renders a plausible date |
| A shared design-system Button gets its props from dozens of screens that other teams own | Test minimally: one interaction test for each prop that changes behaviour, such as `type` and `disabled` | Change frequency falls to rarely and Detectability moves to eventually, because a break shows on screens its author never opens |
| The component formats a price prop, such as an amount in cents on an order summary | Test mandatory: assert the exact string for each currency and rounding case | Blast radius rises to money and Reversibility to costly, because customers buy at the amount shown |
What breaks if you don't test
For display props, nothing breaks that the author misses. The failures that get through are props whose effect nobody sees: a component stops calling onSave, a shared Button drops the type it receives, or a layout component swaps two dates on the way to a child. The screen looks the same, so the first person to notice is a customer.
What you lose if you over-test
A test for each display prop repeats the JSX: render with name="Ana", assert that "Ana" appears. A card with eight props gets eight such tests, and a redesign that moves the name into a tooltip breaks all eight with no bug behind them. A card that passes onSelect straight to a button shows full coverage from render tests alone, although no test clicks it.
What to do instead
Sort props by what they do, and write Testing Library unit tests only where a prop changes behaviour:
- Props the user acts through, such as
onChange,disabledandtype: one user-event test each, asserting the callback's arguments or that nothing fires. - Two or more forwarded props of one type: one test with a distinct value in each.
- Props that only set content or style: type them, as the React TypeScript guide shows, and look at the result.
Query by role and label, per the Testing Library guiding principles, so a markup change that keeps the behaviour passes.
When the answer changes
- The component is shared across many screens that other teams own.
- A prop carries data that the app saves, or a price.
- The codebase has no prop types, so a renamed prop arrives as undefined.
Real incident + Code example
The Add button that submitted the form
On a recruiting app I worked on, the design-system Button typed its props as the HTML button attributes plus variant and size, so TypeScript accepted type="button". The component passed only variant, size, onClick and children to the element, and a <button> inside a form with no type is a submit button. "Add phone number" in the candidate form submitted and closed the form before the new row was filled in. Storybook shows the button with no form around it, and the first report came from a recruiter four days later. The fix passed the remaining props through, and this test joined the Button suite:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test, vi } from 'vitest';
import { Button } from './Button';
test('a Button with type="button" does not submit its form', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn((event) => event.preventDefault());
render(
<form onSubmit={onSubmit}>
<Button type="button">Add phone number</Button>
</form>,
);
await user.click(screen.getByRole('button', { name: 'Add phone number' }));
expect(onSubmit).not.toHaveBeenCalled();
});
The suite has no test for variant or size, because a wrong colour shows as soon as someone opens the story.
Related questions
FAQ
- Should I test all component props?
No, test only the props that change what the component does, such as callbacks,
disabledandtype, with one interaction test each. Props that only set text or style need no test in a TypeScript codebase.- Should I test that a component passes props to its children?
No, a component that passes props of different types to children with their own tests needs no forwarding test, because TypeScript rejects a prop of the wrong type. Add one test when two props of one type pass through, because a swap renders a plausible value.
- Does TypeScript replace tests for component props?
TypeScript replaces tests that check whether a prop is present and has the right type. A callback that the component never calls and a prop that it accepts and drops both compile, so each prop that changes behaviour still needs one interaction test.