Async & Promises
Test callbacks, promises, async/await, and control time with fake timers.
Callback-Style Async
Legacy callback APIs can use done callback parameter—call done() on success or done(error) on failure. Jest fails test if done not called or called multiple times.
Prefer promisifying callbacks or testing promise-based APIs directly—done callback tests are harder to maintain.
test("calls back with result", (done) => {
fetchData((err, data) => {
expect(err).toBeNull();
expect(data).toBe("ok");
done();
});
});Testing Promises
Return promise from test function so Jest waits for resolution. Use resolves and rejects matchers for readability.
Always return or await async expectations—floating promises cause false passes when assertion fails after test completes.
test("resolves with user", () => {
return expect(fetchUser("1")).resolves.toEqual({ id: "1" });
});
test("rejects on missing", async () => {
await expect(fetchUser("missing")).rejects.toThrow("Not found");
});Async/Await Tests
Mark test async and use await inside for sequential async steps. try/catch around awaited calls when testing error paths without rejects matcher.
Use Promise.all in tests when verifying parallel operations complete—match production concurrency patterns.
- Set jest.setTimeout(ms) for slow integration tests
- Use waitFor from Testing Library for DOM async updates in component tests
- Avoid arbitrary setTimeout in tests—use fake timers or await conditions
test("creates order", async () => {
const order = await createOrder({ items: [{ sku: "A", qty: 1 }] });
expect(order.status).toBe("pending");
});Fake Timers
jest.useFakeTimers() mocks setTimeout, setInterval, and Date. Advance time with jest.advanceTimersByTime or runAllTimers.
Restore real timers in afterEach with jest.useRealTimers(). Modern fake timers support async scheduling in many cases.
jest.useFakeTimers();
test("debounces input", () => {
const fn = jest.fn();
debounce(fn, 300)();
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(fn).toHaveBeenCalled();
});Flaky Async Test Prevention
Race conditions cause flaky tests—await explicit conditions, not fixed delays. Isolate tests from real network with mocks or MSW.
In CI, run tests multiple times (jest --repeat) when hunting flakes. Log unhandled promise rejections—they fail suites unpredictably.
- Configure testEnvironmentOptions for stable jsdom URL if needed
- Clean up intervals and subscriptions in afterEach
- Use --detectOpenHandles to find hanging async resources