Jest · Unit tests that survive refactors

How do resolves and rejects matchers work?

MediumpromisesresolvesrejectsUpdated Oct 2026
Quiz this track

Short answer

resolves and rejects await a promise and apply a matcher to its settled value or reason. They must themselves be awaited or returned, otherwise the assertion can run outside the test.

These matchers unwrap a promise so you can assert on the settled result without writing a try/catch. resolves follows the fulfilled path, and rejects follows the rejected path. Both return a promise, which is why the await is not optional.

ts
test("resolves with the parsed body", async () => {
  await expect(loadUser("u1")).resolves.toEqual({ id: "u1" });
});

test("rejects with a readable error", async () => {
  await expect(loadUser("missing")).rejects.toThrow("not found");
});

What each path expects

  • resolves fails if the promise rejects
  • rejects fails if the promise fulfils
  • rejects.toThrow matches an error's message or constructor
  • rejects.toMatchObject checks fields on an error object
  • resolves.toBeUndefined is valid for a void async function

A common mistake is asserting rejects on a function that throws synchronously. If the function is not async and throws before returning a promise, the call throws immediately and the assertion never sees a rejected promise. Wrap the call in an async function or use toThrow in that case.

When you assert rejection, also confirm the operation did not leave state behind, such as a cached failure or a partially written record. The rejection is only half of the contract.

Follow-ups you should expect

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

  1. 1What happens if you forget await before expect(promise).resolves?
  2. 2How do you assert on the error message of a rejection?
  3. 3Why does rejects fail when a function throws synchronously?
  4. 4How do you assert that an async function resolves to undefined?

Say it in the interview

resolves and rejects unwrap a promise and apply a normal matcher to the settled value or error. I always await them, because otherwise the assertion escapes the test. For rejections I assert on the message or on fields, not the whole stack. If a function throws synchronously, I use toThrow instead, because there is no promise to reject.

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.