Skip to content

Fix: Jest Timeout — Exceeded timeout of 5000ms for a test

FixDevs ·

Quick Answer

How to fix Jest 'Exceeded timeout of 5000ms for a test' errors caused by unresolved promises, missing done callbacks, async/await mistakes, and slow database or network calls in tests.

The Error

You run Jest tests and one or more fail with:

Thrown: "Exceeded timeout of 5000ms for a test.
Add a timeout value to this test to increase the timeout, if this is a long-running test;
see https://jestjs.io/docs/api#testname-fn-timeout."

Or:

● fetchUser › returns user data

  Exceeded timeout of 5000ms for a test.

      at node_modules/jest-jasmine2/build/queueRunner.js:47:12

The test starts, runs for 5 seconds (Jest’s default timeout), then fails — even if the code itself would eventually succeed given more time.

Why This Happens

Jest uses a 5000ms (5 second) default timeout per test. If a test does not complete within that window, Jest forcibly fails it. Common causes:

  • A Promise never resolves or rejects — a missing resolve()/reject() call, or a callback that never fires.
  • done callback never called — using the callback-style async pattern but forgetting to call done().
  • await on a function that never settles — an infinite loop, deadlock, or event that never fires.
  • Actual network or database calls in tests without mocking — real I/O is slow and flaky.
  • beforeAll or beforeEach timing out — setup hooks count against the timeout too.
  • Missing return on a promise inside a test — Jest does not know to wait for it.

Fix 1: Return or Await the Promise

The most common mistake: the test completes synchronously, then the promise settles after Jest has already moved on — or Jest doesn’t wait at all.

Broken — promise not returned:

test("fetches user data", () => {
  fetchUser(1).then(user => {
    expect(user.name).toBe("Alice"); // Jest doesn't wait for this
  });
  // Test completes immediately — no assertion actually runs
});

Fixed — return the promise:

test("fetches user data", () => {
  return fetchUser(1).then(user => {
    expect(user.name).toBe("Alice");
  });
});

Fixed — use async/await:

test("fetches user data", async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe("Alice");
});

async/await is the clearest pattern. Jest detects that the test function is async and waits for it to resolve before marking the test as done.

Fix 2: Fix the done Callback Pattern

When using the done callback (older pattern), Jest waits until done() is called. If it is never called — due to an error or a missing branch — the test times out.

Broken — done not called on error:

test("sends notification", (done) => {
  sendNotification("[email protected]", (err, result) => {
    if (err) {
      // Forgot to call done() or done(err)
      console.error(err);
      return;
    }
    expect(result.sent).toBe(true);
    done();
  });
});

Fixed — always call done:

test("sends notification", (done) => {
  sendNotification("[email protected]", (err, result) => {
    if (err) {
      done(err); // Pass the error to done — fails the test with the actual error
      return;
    }
    expect(result.sent).toBe(true);
    done();
  });
});

Calling done(err) with an error argument fails the test immediately with a descriptive error instead of timing out.

Pro Tip: Prefer async/await over the done callback pattern. It is easier to read, less error-prone, and avoids the “forgot to call done” class of bugs entirely. Only use done when working with callback-based APIs that cannot be promisified.

Fix 3: Mock Network and Database Calls

Tests that make real HTTP requests or database queries are slow, flaky, and environment-dependent. Mock them instead:

Broken — real HTTP call:

test("loads posts from API", async () => {
  const posts = await fetch("https://jsonplaceholder.typicode.com/posts")
    .then(r => r.json());
  expect(posts.length).toBeGreaterThan(0);
  // Fails if the network is slow, the API is down, or the test runs in CI
});

Fixed — mock with jest.fn() or MSW:

// Using jest.spyOn to mock fetch
global.fetch = jest.fn().mockResolvedValue({
  ok: true,
  json: async () => [{ id: 1, title: "Post 1" }, { id: 2, title: "Post 2" }],
});

test("loads posts from API", async () => {
  const posts = await loadPosts();
  expect(posts.length).toBe(2);
  expect(global.fetch).toHaveBeenCalledWith("/api/posts");
});

Using jest.mock() for modules:

jest.mock("./api", () => ({
  fetchUser: jest.fn().mockResolvedValue({ id: 1, name: "Alice" }),
}));

import { fetchUser } from "./api";

test("fetchUser returns user", async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe("Alice");
});

For database calls, use an in-memory database (SQLite with :memory:, mongodb-memory-server for MongoDB) or mock the database layer entirely.

Fix 4: Increase the Timeout for Slow Tests

If the test is legitimately slow (integration test, large file processing, real database), increase the timeout:

Per-test timeout:

test("processes large CSV file", async () => {
  const result = await processCSV("large-file.csv");
  expect(result.rows).toBe(100000);
}, 30000); // 30 second timeout for this test only

Per-suite timeout in beforeAll:

describe("database integration tests", () => {
  beforeAll(async () => {
    await setupDatabase();
  }, 60000); // 60 seconds for setup

  test("queries users table", async () => {
    const users = await db.query("SELECT * FROM users");
    expect(users.length).toBeGreaterThan(0);
  }, 15000); // 15 seconds per test
});

Global timeout for all tests:

In jest.config.js:

module.exports = {
  testTimeout: 15000, // 15 seconds for all tests
};

Or in jest.config.ts:

import type { Config } from "jest";

const config: Config = {
  testTimeout: 15000,
};

export default config;

Common Mistake: Setting a very high global timeout (e.g., 60000ms) to “fix” timeouts hides real problems. A test that times out at 5 seconds would just time out at 60 seconds instead. Increase timeout only for tests that are genuinely slow by design, and fix the underlying issue (missing mock, unresolved promise) for everything else.

Fix 5: Fix beforeAll and afterAll Timeouts

Setup and teardown hooks have their own timeout (same as the test timeout by default). Database connections and server startup can be slow:

Broken — beforeAll times out:

beforeAll(async () => {
  await startServer(); // Takes 8 seconds — times out at 5
  await seedDatabase();
});

Fixed — pass timeout as second argument:

beforeAll(async () => {
  await startServer();
  await seedDatabase();
}, 30000); // 30 seconds for setup

afterAll(async () => {
  await stopServer();
  await clearDatabase();
}, 15000);

Fix 6: Fix Promises That Never Settle

If a promise hangs indefinitely, no timeout increase will fix it — find why it never resolves:

Common causes:

// Broken — resolve/reject never called
const wait = () => new Promise((resolve, reject) => {
  someEmitter.on("done", () => {
    // resolve() is missing — promise hangs forever
    console.log("done");
  });
});

// Broken — awaiting a non-promise
async function test() {
  await undefined; // Immediately resolves, but if you await a never-resolving thing:
  await new Promise(() => {}); // Hangs forever
}

Diagnose with a race and timeout:

const withTimeout = (promise, ms) =>
  Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms)
    ),
  ]);

test("operation completes", async () => {
  const result = await withTimeout(myOperation(), 3000);
  expect(result).toBe("done");
});

This gives you a descriptive error (“Timed out after 3000ms”) instead of Jest’s generic timeout message, making it easier to see which operation hangs.

Fix 7: Fix Open Handles Warning

After fixing timeouts, Jest may warn:

Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests.
Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

Open handles (server sockets, database connections, timers) prevent Jest from exiting cleanly. Find them:

npx jest --detectOpenHandles

Then close them in afterAll:

let server;

beforeAll(async () => {
  server = app.listen(3001);
});

afterAll(async () => {
  await new Promise(resolve => server.close(resolve)); // Close the server
  await db.end(); // Close database connections
});

For timer-based open handles, use Jest’s fake timers:

beforeEach(() => {
  jest.useFakeTimers();
});

afterEach(() => {
  jest.runOnlyPendingTimers();
  jest.useRealTimers();
});

Still Not Working?

Check for circular async dependencies. If function A awaits function B, and B awaits A, both hang indefinitely. Add logging inside each function to trace where execution stops.

Check event listeners that never fire. If a promise resolves only when an event is emitted, and that event never fires (e.g., a stream that never closes), the promise hangs. Add a timeout or verify the event source is working.

Run the specific failing test in isolation:

npx jest --testPathPattern="your-test-file" --verbose

Isolating the test removes interference from other tests and makes the hang easier to reproduce.

Check for missing jest.config.js or package.json jest config. If Jest is picking up the wrong configuration, the testTimeout setting may not apply. Run npx jest --showConfig to see the resolved configuration.

For module import errors that prevent tests from running at all, see Fix: Jest Cannot Find Module.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles