async
testing
test failures
software development
debugging

One failing test causes other async tests to fail

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

A single failing async test can cascade into unrelated failures when shared resources are left in a bad state. The root cause is usually leakage: open handles, pending timers, unhandled rejections, or mutated global state. Fixing the chain reaction requires isolating tests and enforcing reliable cleanup.

Why Async Failures Cascade

Async tests often use network mocks, database connections, queues, clocks, and temporary files. If one test throws before teardown runs, subsequent tests start with contaminated runtime state.

Common leak vectors include:

  • Pending promises that reject after test completion.
  • Fake timers not restored to real timers.
  • Mock servers still intercepting requests.
  • Shared singleton state mutated without reset.

The next tests fail even if their business logic is correct.

Example in Jest

This example shows how a leaked timer from one test can affect another.

javascript
1import { afterEach, describe, expect, it, jest } from "@jest/globals";
2
3let counter = 0;
4
5function scheduleIncrement() {
6  setTimeout(() => {
7    counter += 1;
8  }, 50);
9}
10
11describe("counter tests", () => {
12  afterEach(() => {
13    counter = 0;
14    jest.useRealTimers();
15    jest.clearAllTimers();
16  });
17
18  it("fails early and leaks fake timers", () => {
19    jest.useFakeTimers();
20    scheduleIncrement();
21    throw new Error("boom");
22  });
23
24  it("expects clean timer state", () => {
25    expect(jest.isMockFunction(setTimeout)).toBe(false);
26  });
27});

If cleanup is unreliable, second tests become flaky and misleading.

Isolation Patterns That Work

Use strict setup and teardown for every test scope.

javascript
1beforeEach(async () => {
2  await testDb.reset();
3  server.resetHandlers();
4});
5
6afterEach(async () => {
7  await testDb.closeOpenTransactions();
8  server.close();
9  jest.useRealTimers();
10  jest.restoreAllMocks();
11});

Reset data, mocks, timers, and network interceptors every time. Treat cleanup as mandatory, not optional.

For expensive fixtures, use per-suite resources carefully and enforce invariant checks before each test starts.

Handle Unhandled Rejections Explicitly

Unhandled rejections can surface after a test finishes and then fail random later tests. Capture them in test setup to fail fast and identify origin.

javascript
1const unhandled = [];
2
3beforeAll(() => {
4  process.on("unhandledRejection", reason => {
5    unhandled.push(reason);
6  });
7});
8
9afterEach(() => {
10  if (unhandled.length > 0) {
11    const reason = unhandled.shift();
12    throw new Error(`Unhandled rejection detected: ${String(reason)}`);
13  }
14});

This turns delayed failures into local failures near the cause.

Controlling Parallelism

Many frameworks run tests in parallel processes. Parallelism improves speed but magnifies shared-resource conflicts if tests touch the same ports, files, or database schemas.

Strategies:

  • Use isolated database schema per worker.
  • Allocate random free ports dynamically.
  • Avoid writing to shared temp paths.
  • Mark truly stateful suites as serial.

In Jest, you can temporarily reduce parallelism while stabilizing suites.

bash
jest --runInBand

Running serially is slower but useful to confirm whether failures are race-related.

Framework-Agnostic Checklist

Regardless of language, stable async tests follow the same principles.

  • Await every async operation. No floating promises.
  • Ensure teardown runs even on test failure.
  • Do not depend on execution order.
  • Avoid global mutable state where possible.
  • Add timeout values that reflect realistic operation windows.

These rules apply to Jest, Mocha, Pytest asyncio, JUnit async flows, and other environments.

Reproducing Flakes Systematically

Intermittent async failures are easier to fix when you can reproduce them quickly. Run suspect suites repeatedly with randomized order and worker counts. If failures only appear under parallel load, focus on shared resources first.

bash
jest path/to/suite.test.js --runInBand --repeatTests=20

Then run with normal parallel settings and compare failure signatures. This narrows the bug from generic flakiness to a specific isolation gap.

Common Pitfalls

  • Throwing before cleanup logic executes. Fix by placing cleanup in framework hooks, not inline test bodies.
  • Reusing shared mocks across suites. Fix by resetting mocks and handlers per test.
  • Leaving async operations unawaited. Fix by returning or awaiting promises in every test.
  • Hiding resource conflicts behind high parallelism. Fix by isolating resources or running targeted suites serially.
  • Ignoring random flakiness signals. Fix by treating flaky failures as deterministic bugs with missing isolation.

Summary

  • Cascading async failures usually come from leaked state, not multiple independent bugs.
  • Strict teardown for timers, mocks, network, and data is essential.
  • Capture unhandled rejections to localize failures quickly.
  • Isolate shared resources across parallel workers.
  • Build test suites so each test can run alone and in any order.

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.