Skip to content

Async Testing in Jest

Asynchronous code — promises, async/await, callbacks, and timers — needs special handling in tests, or Jest may mark a test as passed before the async work even finishes. This lesson introduces the core problem and the tools Jest provides to solve it.

Why Async Code Needs Special Handling

By default, Jest considers a test finished as soon as its callback function returns. If that callback kicks off an asynchronous operation (like a promise) but returns immediately without waiting for it, Jest moves on to the next test before the async assertion ever runs — and a failing assertion inside that callback won't fail the test at all.

Jest gives you several ways to tell it "wait for this async work to finish before deciding pass or fail": returning a promise, using async/await, or accepting a done callback for older callback-style APIs.

// WRONG: Jest doesn't wait for the promise
test('fetches a user (broken)', () => {
  fetchUser(1).then((user) => {
    expect(user.name).toBe('Ada'); // this might never even run before the test "passes"
  });
});

// RIGHT: returning the promise tells Jest to wait
test('fetches a user (correct)', () => {
  return fetchUser(1).then((user) => {
    expect(user.name).toBe('Ada');
  });
});

The only difference is the return keyword — but it's the difference between a real test and a silently broken one.

Three Ways to Handle Async Tests

// 1. Return a promise
test('...', () => { return somePromise; });

// 2. async/await (most common today)
test('...', async () => { await somePromise; });

// 3. done callback (legacy callback-based APIs)
test('...', (done) => { doAsyncThing(() => { done(); }); });
  • Returning a promise is the original pattern; Jest waits for it to settle.
  • async/await is the clearest and most widely used approach today.
  • The done callback is for legacy callback-based code that has no promise to return.
  • Never combine done with async/await in the same test function — pick one pattern.

Async Testing Cheat Sheet

The core patterns for telling Jest to wait for async work.

Pattern When to Use
return somePromise Function under test returns a promise
async () => { await ... } Most common; works with try/catch
(done) => { ...; done(); } Callback-based APIs with no promise
await expect(p).resolves... Asserting directly on a resolved promise
await expect(p).rejects... Asserting directly on a rejected promise

The "Silently Passing" Trap

The most dangerous async testing mistake is a test that always shows green, even when the underlying code is broken, because the assertion inside an unreturned promise callback never gets a chance to fail the test. This can hide real bugs for a long time before anyone notices.

Combining async/await (or return) with expect.assertions(n) (covered in the expect() lesson) is the most reliable defense against this trap.

Catching This with a Lint Rule

The eslint-plugin-jest rule jest/valid-expect-in-promise and similar rules can catch unreturned promises inside test callbacks automatically, flagging the mistake before the test is even run.

  • Install eslint-plugin-jest and enable its recommended rule set.
  • jest/valid-expect-in-promise flags promises with expect() calls that aren't returned/awaited.
  • Automated linting catches this mistake far more reliably than manual code review.

Common Mistakes

  • Forgetting to return or await a promise inside a test, letting assertions run after the test already passed.
  • Mixing the done callback pattern with async/await in the same test.
  • Not using expect.assertions() in tests where an assertion might not run at all due to a logic bug.
  • Assuming a green test result means the async code was actually exercised correctly.

Key Takeaways

  • Jest needs to be told to wait for async work: via return, async/await, or a done callback.
  • An unreturned promise inside a test can let assertion failures pass silently.
  • async/await is the clearest, most recommended pattern for modern Jest tests.
  • Lint rules like jest/valid-expect-in-promise help catch this mistake automatically.

Pro Tip

Install eslint-plugin-jest in any serious project. Its rules catch unreturned promises and several other async testing mistakes long before they become a mysteriously "passing" but broken test.