React Testing Interview Questions and Answers

React Testing Library, queries, user events, async assertions, mocking network requests, testing hooks and what is worth testing at all.

10 questions9 mid-level, 1 senior

Testing questions are a shortcut to seeing how you work. The specific API matters far less than the philosophy: are you asserting on what a user can observe, or on how the component happens to be built. Every answer in this section is stronger if you tie it back to that.

1.What is React Testing Library and how does it differ from Enzyme?

Mid-level

React Testing Library renders components and queries them the way a user would, through visible text and accessible roles. Enzyme exposed the component instance, so tests could reach into state and internal methods, which made them break on every refactor.

The guiding principle is that the more your tests resemble the way the software is used, the more confidence they give you. A test that asserts wrapper.state('isOpen') === true passes when the menu is broken and fails when you rename a variable. A test that asserts the menu is visible does the opposite.

  • RTL has no API for reading state, props or instance methods, deliberately.
  • It renders into a real DOM through jsdom, so you test the rendered output rather than a shallow tree.
  • Enzyme is effectively unmaintained and never fully supported React 18, so RTL is the default for new work.

Likely follow-up questions

  • What would you test in a component then?

3.How do you test asynchronous behaviour?

Mid-level

Use findBy queries or waitFor, both of which retry until the assertion passes or the timeout expires. Never assert immediately after an action that triggers an async update, and never reach for a fixed setTimeout.

await user.click(screen.getByRole('button', { name: /load/i }));

// Retries until it appears
expect(await screen.findByText('3 results')).toBeInTheDocument();

// For assertions that are not a query
await waitFor(() => expect(onLoad).toHaveBeenCalledTimes(1));

Likely follow-up questions

  • What is the act warning telling you?

4.What is the difference between fireEvent and userEvent?

Mid-level

fireEvent dispatches a single DOM event. userEvent simulates the full interaction a real user produces — for a click that is pointer, mouse down, focus, mouse up and click — so it catches bugs fireEvent walks straight past.

const user = userEvent.setup();

await user.type(screen.getByLabelText('Email'), 'a@b.com');
await user.click(screen.getByRole('button', { name: /submit/i }));

userEvent.type fires a keydown, keypress, input and keyup per character, so a field that only handles keydown behaves realistically. It also refuses to click a disabled element, which fireEvent will happily do. Use userEvent by default; drop to fireEvent only for events a user cannot produce, such as scroll.

Likely follow-up questions

  • Why does userEvent need await?

5.How do you mock network requests in tests?

Mid-level

Intercept at the network layer with Mock Service Worker rather than stubbing fetch or the module that calls it. The component then runs its real data code and the test stays valid when you change HTTP client.

const server = setupServer(
  http.get('/api/users/:id', ({ params }) =>
    HttpResponse.json({ id: params.id, name: 'Ada' })
  )
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
  • Handlers can be overridden per test to return a 500 or a slow response, so error and loading states are actually covered.
  • The same handlers work in tests and in a development environment with no backend.
  • jest.mock('./api') couples the test to your module layout and stops testing the code that builds the request.

Likely follow-up questions

  • How would you test the error state?

6.How do you test a custom hook?

Mid-level

With renderHook from React Testing Library, wrapping state updates in act. If the hook only exists to serve one component, testing it through that component is often more valuable.

const { result } = renderHook(() => useCounter(3));

expect(result.current.count).toBe(3);

act(() => result.current.increment());

expect(result.current.count).toBe(4);

result.current is re-read after each render, so hold onto result rather than destructuring values out of it. Pass a wrapper option when the hook needs a provider above it.

Likely follow-up questions

  • When would you not test a hook directly?
Practise this in the quiz

7.What should you actually test in a React component?

Mid-level

Behaviour a user depends on: what renders for a given set of props, what happens when they interact, and what the component does in loading, empty and error states. Not implementation details such as state variable names or which hook fired.

  • Renders the right thing for representative props, including the empty and error cases people forget.
  • Interaction produces the expected visible change or calls the expected callback.
  • Conditional and accessibility-relevant attributes: disabled states, aria-invalid, focus after an action.
  • Not: internal state, the number of renders, whether useMemo was used, or exact CSS classes.

Likely follow-up questions

  • How much coverage is enough?

8.What is the difference between unit, integration and end to end tests in a React project?

Mid-level

Unit tests cover a single function or component in isolation, integration tests cover several components working together with mocked network calls, and end to end tests drive the real application in a browser against a real or seeded backend.

SpeedConfidenceTypical tool
UnitMillisecondsLow on its ownVitest or Jest
IntegrationFastHigh per unit of effortRTL plus MSW
End to endSlow, flaky if carelessHighestPlaywright or Cypress

The pragmatic position, and the one most interviewers share, is that component integration tests are the sweet spot in React: they exercise real user flows without the cost of a browser, and a small number of end to end tests then cover the critical paths such as sign in and checkout.

Likely follow-up questions

  • Where would you put the majority of your tests?

9.What causes the "not wrapped in act(...)" warning?

Senior

A state update happened outside anything React was told to wait for, usually an async update that resolved after the test finished its assertions. The fix is almost always to await the resulting change rather than to wrap something in act.

// Warns: the fetch resolves after the test has moved on
render(<Profile id='1' />);
expect(screen.getByText('Loading')).toBeInTheDocument();

// Fixed: wait for the update the component will make
render(<Profile id='1' />);
expect(await screen.findByText('Ada')).toBeInTheDocument();

act tells React to flush effects and updates before you assert. Testing Library already wraps its own render and userEvent calls, so a warning usually means there is an update in flight that the test never waited for — which is a real gap, not just noise.

Likely follow-up questions

  • Why is silencing the warning a bad idea?

10.How do you test a component that depends on context, a router or a store?

Mid-level

Write a custom render that wraps the component in the same providers the app uses, and export it instead of RTL's render. Every test then gets the real environment without repeating setup.

function renderWithProviders(ui, { route = '/' } = {}) {
  window.history.pushState({}, '', route);

  return render(ui, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={new QueryClient()}>
        <BrowserRouter>{children}</BrowserRouter>
      </QueryClientProvider>
    ),
  });
}

Likely follow-up questions

  • How do you test a specific route?

All React interview questions