Jest · Unit tests that survive refactors

How do you test errors and failure paths in Jest?

MediumerrorstoThrowfailure pathsUpdated Oct 2026
Quiz this track

Short answer

Assert thrown errors with toThrow, and match the message or type rather than the full stack. For async failures use rejects, and test the user-facing error state as well.

Failure paths are part of the contract. A function that throws a typed error, a UI that shows a retry message, and a retry that gives up after three attempts all deserve tests. Matching only that something threw is weak because any unrelated bug also throws.

ts
test("rejects an invalid total", () => {
  expect(() => createInvoice({ total: -1 })).toThrow(ValidationError);
  expect(() => createInvoice({ total: -1 })).toThrow("total must be positive");
});

test("shows a retry message when the API fails", async () => {
  server.use(
    http.get("/api/items", () => new HttpResponse(null, { status: 500 })),
  );

  render(<ItemList />);

  expect(await screen.findByRole("alert")).toHaveTextContent("Try again");
});

What to assert on an error

  • The error type, using the constructor when you have a custom class
  • A stable part of the message, not the full stack
  • The user-visible error state and any recovery action
  • That state was not partially mutated after the failure
  • That a retry or fallback ran the expected number of times

For async code, rejects.toThrow handles a rejected promise. If the function throws synchronously, use toThrow with a wrapper function instead, because there is no promise to await. Passing a function to toThrow is required; expect(fn()).toThrow() throws before the matcher runs.

Do not assert on a stack trace. Stack lines change with refactors, bundling and Node versions, and they are not part of the behaviour you want to protect.

Follow-ups you should expect

Interviewers use these to check whether the first answer was understanding or memorisation.

  1. 1Why is expect(fn()).toThrow() wrong?
  2. 2How do you assert on a custom error class?
  3. 3How do you test a retry that eventually gives up?
  4. 4Why should you test the UI error state separately from the thrown error?

Say it in the interview

I test failure paths as deliberately as success paths. For sync code I pass a wrapper function to toThrow and match the error type or a stable part of the message. For async code I use rejects. I also render the UI and assert on the visible error and recovery action, because a correct thrown error can still produce a blank screen. I never assert on stack traces.

Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.

Related questions

Reading is step one

Now say it out loud.

Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Jest mock interview and get scored feedback on depth, clarity and confidence.