The test that passes because it never ran
The test function returned immediately. The assertion ran later, on an already-finished test, and if it threw, the failure was reported against some other test — or nowhere at all. Await it, or return the promise. Every runner treats a returned promise as "wait for this".
it("saves the order", () => {
save(order).then((saved) => {
expect(saved.status).toBe("paid"); // this runs AFTER the test finished
});
}); // …so the test passed, whatever the answer was
it("saves the order", async () => {
const saved = await save(order);
expect(saved.status).toBe("paid");
});
it("saves the order", () => save(order).then((saved) => {
expect(saved.status).toBe("paid");
}));
Testing a rejection
The rule: a test about failure must fail when the failure does not happen. A bare try/catch with the assertion inside is the version everybody writes once, and it is green on the day the bug appears.
// wrong: if load() resolves, the test passes silently
it("fails for a missing order", async () => {
try {
await load(999);
} catch (error) {
expect(error.message).toContain("not found");
}
});
// right: assert that it rejected, and why
it("fails for a missing order", async () => {
await expect(load(999)).toReject("not found");
});
// or, without a matcher for it
it("fails for a missing order", async () => {
let threw = null;
try { await load(999); } catch (error) { threw = error; }
expect(threw).toBeTruthy();
expect(threw.message).toContain("not found");
});
Waiting for something to become true
A fixed sleep is a bet about the speed of a machine you have not met. Polling until a condition holds finishes as soon as it is true — usually in a few milliseconds — and fails with a clear timeout rather than an assertion about the wrong moment.
// flaky: too short on a loaded machine, and slow on every other run
await new Promise((r) => setTimeout(r, 500));
expect(list.children).toHaveLength(3);
// stable: check often, give up eventually
await waitFor(() => list.children.length === 3, { timeout: 1000 });
If the thing you are waiting for is your own timer, use fake timers from the last lesson. Waiting is for things you genuinely do not control — a real network, a real browser.
Parallel, and the order of failures
const [a, b] = await Promise.all([load(1), load(2)]); // first rejection wins
const results = await Promise.allSettled([load(1), load(2)]); // every outcome
expect(results.map((r) => r.status)).toEqual(["fulfilled", "rejected"]);
Timeouts
Every runner kills a test that takes too long — five seconds by default in most. A test that hits that limit is usually not slow: it is a test that awaits a promise which never settles, because the thing that was supposed to resolve it never happened. Read a timeout as "this test is waiting for something that will not arrive", not as "raise the limit".