Jest
async testing
asynchronous
test completion
JavaScript

Jest finishing async test before done

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A common Jest problem is tests that appear to pass while async work has not finished. This happens when the test function does not return a Promise, does not await, or incorrectly uses done together with Promise-based code. The result is false positives: CI is green, but behavior is untested.

Reliable async testing in Jest requires one clear completion mechanism per test. Either return/await a Promise, or use done for callback-style APIs. Mixing patterns is where most mistakes start.

Core Sections

1. Promise-based test must return or await

javascript
1test("loads user", async () => {
2  const user = await fetchUser();
3  expect(user.id).toBe(1);
4});

Equivalent Promise-return style:

javascript
1test("loads user", () => {
2  return fetchUser().then(user => {
3    expect(user.id).toBe(1);
4  });
5});

2. Use done only for callback APIs

javascript
1test("callback API", done => {
2  readConfig((err, cfg) => {
3    if (err) return done(err);
4    expect(cfg.mode).toBe("prod");
5    done();
6  });
7});

Call done(err) on failure paths, otherwise Jest may hang or misreport.

3. Avoid mixing done with async/await

Anti-pattern:

javascript
1// do not do this
2test("bad mix", async done => {
3  const value = await getValue();
4  expect(value).toBe(1);
5  done();
6});

Pick one model. For Promise-based code, drop done.

4. Assert async failures correctly

javascript
test("rejects on invalid token", async () => {
  await expect(login("bad-token")).rejects.toThrow("unauthorized");
});

Without await, rejection assertions may not run before test ends.

5. Use fake timers carefully

When timers are mocked, advance timers before asserting results.

javascript
1jest.useFakeTimers();
2
3test("delayed callback", () => {
4  const cb = jest.fn();
5  setTimeout(cb, 1000);
6  jest.advanceTimersByTime(1000);
7  expect(cb).toHaveBeenCalled();
8});

Timer state leakage between tests can also create flaky async behavior.

6. Add expect.assertions for callback safety

javascript
1test("callback executes", done => {
2  expect.assertions(1);
3  asyncWork(result => {
4    expect(result).toBeTruthy();
5    done();
6  });
7});

This catches tests that exit before expected assertions run.

Common Pitfalls

  • Forgetting to return or await a Promise in an async test.
  • Mixing done with Promise/async syntax in one test body.
  • Not wiring error paths to done(err) in callback tests.
  • Using rejection assertions without await.
  • Leaving fake timer state unreset between tests.

Summary

Jest finishing async tests too early is usually a completion-signaling bug. Use one pattern per test: return/await Promise or callback done, not both. Add explicit rejection assertions, timer control, and assertion counts where appropriate. With consistent async test style, false positives disappear and test results become trustworthy.

For teams maintaining jest finishing async test before done in long-lived codebases, reliability improves when implementation guidance is paired with a lightweight verification routine. A practical pattern is to define three test categories up front. First, happy-path tests that validate normal expected inputs. Second, boundary tests that include empty values, minimum and maximum limits, and malformed records from real logs. Third, operational tests that simulate production-like behavior under retries, parallel execution, and partial failure. This combination catches both obvious logic defects and the subtle integration issues that usually appear after deployment.

It is also useful to encode assumptions close to the code rather than leaving them in scattered documentation. Add short comments where invariants matter, keep helper utilities centralized, and avoid repeating slightly different logic in multiple modules. In CI, run a small deterministic suite on every commit and a broader dataset suite on schedule. When incidents occur, convert the failing scenario into a permanent regression test before patching. Over time this creates a strong feedback loop where jest finishing async test before done behavior remains stable even as dependencies, framework versions, and team ownership change. The result is less firefighting and faster review cycles. A single flaky-test dashboard with trend history also helps teams spot async reliability regressions early.


Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.