Confidence through the user's eyes
Test behaviour the way users experience it, fake the network rather than your code, and use TypeScript to catch prop and event mistakes before they ship.
- Testing LibraryQueries by role, user events and async.
- MockingMSW for APIs; renderHook for hooks.
- TypeScriptProps, events, refs and generics.
Teams expect React developers to test their components and use TypeScript. Interviewers ask how you test what users see (not implementation details), how you fake APIs, and how you type props, events and hooks.
1. What is the philosophy of React Testing Library?
“The more your tests resemble the way your software is used, the more confidence they give you.” Find elements the way users do — by role, label and text — interact with them, and assert on what appears. Avoid testing internal state or implementation details, so refactors do not break tests.
test('adds a todo', async () => {
const user = userEvent.setup();
render(<TodoApp />);
await user.type(screen.getByRole('textbox', { name: /new todo/i }), 'Revise DSA');
await user.click(screen.getByRole('button', { name: /add/i }));
expect(screen.getByRole('listitem')).toHaveTextContent('Revise DSA');
});2. getBy, queryBy and findBy — what is the difference?
getBy* throws if the element is missing — for things that should be there. queryBy* returns null — for asserting something is absent. findBy* returns a promise that waits — for elements that appear after async work.
expect(screen.queryByText(/error/i)).not.toBeInTheDocument();
expect(await screen.findByText(/welcome, asha/i)).toBeVisible();3. How do you test a component that calls an API?
Mock the network, not your fetch function: Mock Service Worker (MSW) intercepts requests at the network layer and returns fake responses, so the component's real data code runs. Test the loading, success and error paths.
const server = setupServer(
http.get('/api/orders', () => HttpResponse.json([{ id: 1, total: 499 }])),
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('shows orders', async () => {
render(<Orders />);
expect(await screen.findByText('₹499')).toBeInTheDocument();
});
test('shows an error', async () => {
server.use(http.get('/api/orders', () => new HttpResponse(null, { status: 500 })));
render(<Orders />);
expect(await screen.findByRole('alert')).toHaveTextContent(/could not load/i);
});4. How do you test a custom hook?
Use renderHook from Testing Library and wrap state changes in act. Often the better test is through a small component that uses the hook the way real code does.
test('useCounter increments', () => {
const { result } = renderHook(() => useCounter(5));
act(() => result.current.increment());
expect(result.current.count).toBe(6);
});5. How do you type component props in TypeScript?
Define a props type or interface, mark optional props with ?, give defaults in destructuring, and type children with React.ReactNode. Extend native element props with ComponentProps<"button"> for wrapper components.
type ButtonProps = React.ComponentProps<'button'> & {
variant?: 'primary' | 'ghost';
loading?: boolean;
};
export function Button({ variant = 'primary', loading = false, children, ...rest }: ButtonProps) {
return (
<button className={`btn btn--${variant}`} disabled={loading || rest.disabled} {...rest}>
{loading ? 'Please wait…' : children}
</button>
);
}6. How do you type events, state and refs?
Let TypeScript infer where it can. Annotate state when the initial value does not show the full type (null now, an object later). Use React's event types for handlers and give refs the element type.
const [user, setUser] = useState<User | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}7. How do you write a generic component?
Add a type parameter so the component works with any item type while keeping type safety — common for lists, tables and selects.
type ListProps<T> = {
items: T[];
getKey: (item: T) => string | number;
render: (item: T) => React.ReactNode;
};
function List<T>({ items, getKey, render }: ListProps<T>) {
return <ul>{items.map(item => <li key={getKey(item)}>{render(item)}</li>)}</ul>;
}
<List items={users} getKey={u => u.id} render={u => u.name} />8. What should and should not be tested in a React app?
Test user-visible behaviour of components, important flows (sign-up, checkout), custom hooks and utility logic, and error states. Do not test library internals, implementation details such as state variable names, or styling. Add a few end-to-end tests (Playwright) for critical journeys.
Remember
Key points
- Query by role and label
Like a user or screen reader would.
- findBy for async UI
It waits for elements to appear.
- Mock the network
MSW keeps real data code under test.
- Extend native props
ComponentProps<"button"> for wrappers.
Interview drill
Follow-up questions
What the interviewer asks after your first answer. Turn on practice mode, answer each one aloud, then reveal.
-
1 Why prefer userEvent over fireEvent? Basic
userEvent simulates full interactions (focus, keydown, input, keyup) like a real user; fireEvent dispatches a single event.
-
2 What is snapshot testing and when is it useful? Intermediate
It saves rendered output and fails when it changes. Useful for small stable outputs; large snapshots get approved blindly and catch little.
-
3 How do you test a component that uses context? Intermediate
Render it inside the real provider (or a test provider) via a custom render helper that wraps providers.
-
4 type vs interface for props? Basic
Either works. Interfaces can be extended and merged; types handle unions and mapped types. Pick one convention per codebase.
-
5 How do you type a custom hook that returns a tuple? Intermediate
Return
[value, setValue] as constor annotate the return type, so callers get a typed tuple instead of an array of a union. -
6 What are flaky tests and how do you avoid them in React? Advanced
Tests that pass or fail randomly, usually from timing. Use findBy and waitFor instead of fixed sleeps, control timers with fake timers, and isolate network with MSW.