Frontend Testing Interview Questions

Testing rounds are about judgement: what a unit is, what to mock, and whether a passing suite would actually have caught the bug.

This topic has 24 curated Frontend Testing questions covering unit boundaries, mocking, async tests, flakiness, coverage and CI strategy. Below are 8 of them in full, with the same answers you get in the interactive tool.

Practise all 24 Frontend Testing questions →

Difficulty breakdown

  • Easy4
  • Medium11
  • Hard8
  • Expert1

Question formats

  • Multiple choice6
  • Debugging4
  • Concept3
  • Scenario3
  • Best practice2
  • Write code2

What these questions cover

  • strategy
  • mocking
  • async
  • ci
  • flakiness
  • coverage
  • integration
  • assertions
  • debugging
  • factories

8 example Frontend Testing interview questions

EasyBest practice

You have just fixed a production bug. What test do you write, and when do you write it?

One that reproduces the bug at the lowest level that actually fails, written *before* the fix so you can watch it fail for the right reason, then pass. Assert on the specific broken behaviour, not on everything nearby.

Writing it first is the part people skip, and it is the part that gives the test meaning: a test written after the fix has never demonstrated that it can detect the bug, and a surprising number of them cannot. The "lowest level that fails" caveat keeps you honest too — if the bug only reproduces end to end, that is information about where the real gap is, not a reason to assert something adjacent in a unit test and call it covered.

MediumWrite code

Test a component that fetches on mount, covering all three states it can be in

it('renders the user once loaded', async () => {
  server.use(http.get('/api/user', () => HttpResponse.json({ name: 'Ada' })));
  render(<UserCard id="1" />);
  expect(screen.getByRole('status')).toBeInTheDocument();      // loading
  expect(await screen.findByRole('heading', { name: 'Ada' })).toBeInTheDocument();
  expect(screen.queryByRole('status')).not.toBeInTheDocument(); // loading gone
});

Stub the network at the boundary (MSW or a fetch mock), render, assert the loading affordance synchronously, then `await screen.findByText(...)` for the resolved content — `findBy*` retries until the element exists, so nothing needs a timer. Re-stub with a failing response for the error case. Query by role and accessible name so the test breaks on behaviour changes, not on markup changes.

The error case is the one that gets skipped and the one that ships broken, because it is invisible in manual testing. Asserting the loading state disappears matters as much as asserting the data appears — a component that renders both at once passes a naive success assertion. Stubbing at the network boundary rather than mocking the component's own data hook is what keeps the test honest about the wiring in between.

MediumCode review

Review this test — three modules are mocked before it starts

vi.mock('./api');
vi.mock('./format');
vi.mock('./validate');

it('submits the form', async () => {
  render(<Form />);
  await user.click(screen.getByRole('button'));
  expect(api.submit).toHaveBeenCalled();
});

Almost everything real has been mocked away, so the test asserts only that a click reaches a mock — it would still pass if formatting and validation were both broken, and it will keep passing after their signatures change. Mock at a real boundary (the network, via MSW or a fetch stub) and let your own modules run, so the test exercises the integration it claims to. The assertion is also weak: check WHAT was submitted, not merely that something was. And clicking a button by role is good, but the test never verifies the user-visible outcome of the submission.

Over-mocking produces tests that are immune to the bugs they were written to prevent — the drift is silent because they stay green.

EasyConcept

What counts as a "unit" in a frontend test, and why does the answer affect test quality?

A unit is best defined as a unit of BEHAVIOUR, not a file or a function. If you define it as "one module with everything else mocked", you end up asserting that your code calls your other code — tests that break on refactors and pass through real bugs. If you define it as "a component plus the hooks and helpers it genuinely uses, with only the network stubbed", the test exercises a behaviour a user could describe, survives internal restructuring, and fails when something actually breaks. The practical boundary is usually the network and the clock: stub those, run the rest. Defining the unit too small is the most common reason a large suite provides little confidence.

The definition drives everything downstream. Defining a unit as one module with everything mocked produces tests that assert your code calls your other code — they break on every refactor and pass through real bugs. Defining it as a behaviour with only the network stubbed produces tests that survive refactors and fail when something a user would notice breaks.

MediumDebugging

Every PR updates fifty snapshot files and nobody reads the diffs. What is wrong and what do you do?

expect(render(<Dashboard {...props} />).container).toMatchSnapshot();

A whole-component snapshot asserts on everything — class names, wrapper divs, attribute order — so any styling or markup change churns it, and reviewers learn to approve the update without reading it. At that point the snapshot has negative value: it costs review attention and catches nothing, while providing false assurance. Replace with targeted assertions about behaviour and visible output, keep snapshots only for genuinely stable serialisable data (a formatted string, a normalised config object), and consider visual regression testing if the concern is actually appearance. If a snapshot must stay, make it small and inline so the diff is readable in the review.

The failure is social as much as technical: an assertion that is always updated mechanically is not an assertion, and the fix requires acknowledging that.

HardFollow-up

Your end-to-end suite now takes 40 minutes and people ignore it. What would you change?

A suite nobody waits for provides no signal, so treat the runtime as the bug. Shard across parallel machines, since E2E is embarrassingly parallel. Split it by value: a small smoke set on every PR, the full suite on merge or nightly. Attack flakiness directly, because retries hide it and erode trust faster than slowness — track a per-test flake rate and quarantine the worst offenders rather than re-running blindly. Remove tests that duplicate cheaper coverage; an E2E test asserting a validation message belongs at a lower level. Seed data through the API instead of clicking through setup, which is usually where most of the time goes. And make failures diagnosable with traces and screenshots, because a slow suite people cannot debug gets disabled next.

Probes whether the candidate treats CI as a product with users, and knows that flakiness destroys a suite faster than duration.

HardMultiple choice

What is the risk of mocking your own modules deeply?

A mock is a second, unverified copy of an interface. When the real module changes signature or semantics, the mock keeps the old contract and the test keeps passing. Mocking at a real boundary (the network, the clock) is far more stable than mocking internals.

HardScenario

You have two days to add tests to a critical checkout flow. What do you write?

Cover the paths where failure is most expensive, not the code that is easiest to test. One or two end-to-end tests through the happy path of the whole flow, because that is what actually proves the feature works and catches integration breakage nothing else sees. Then integration tests around the highest-risk branches — payment declined, a network failure mid-submit, a double-click producing two orders, and the price or total calculation, since a quiet wrong number is worse than a crash. Unit tests for the pure money and tax logic, which are cheap and exact. Skip snapshot tests of markup entirely; they will fail on every styling change and catch none of these. Then make sure each future bug fix arrives with a regression test so coverage grows where the failures actually are.

Prioritising by cost of failure — and explicitly declining low-value tests — is the judgement being assessed.

The remaining 16 Frontend Testing questions — plus mock interviews, spaced revision and progress tracking — are in the free interview prep workspace.

Related interview topics