npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jest-doctor

v0.1.2

Published

jest environment for leak and issue detection

Readme

jest-doctor main codecov npm version License

jest-doctor is a custom Jest environment that fails tests deterministically when async work leaks across test boundaries. It prevents flaky tests and enforces strong test hygiene.

✨ What problems does it catch?

It detects and reports when tests:

  • Leave unresolved Promises
  • Leave open real or fake timers
  • Rely on excessive real-time delays
  • Emit process / console outputs

Docs


🚀 Quick Start

npm install --save-dev jest-doctor

or

yarn add -D jest-doctor

Add one of the provided environments to your jest.config.js.

export default {
  testEnvironment: 'jest-doctor/env/node',
  // optional
  reporters: ['default', 'jest-doctor/reporter'],
};

Out-of-the-box jest-doctor supports node and jsdom environments. But you can also build your own environment.


⚙️ Configuration

The environment can be configured through the Jest config testEnvironmentOptions:

export default {
  testEnvironmentOptions: {
    report: {
      console: {
        onError: 'warn',
        methods: ['log', 'warn', 'error'],
        ignore: /Third party message/,
      },
      timers: {
        onError: 'warn',
      },
      fakeTimers: {
        onError: 'throw',
      },
      promises: false,
      processOutputs: {
        onError: 'warn',
        methods: ['stderr'],
      },
    },
    delayThreshold: 1000,
    timerIsolation: 'afterEach',
    clearTimers: true,
  },
};

report

Controls which leak types are detected and how they are reported.

Each option can be:

  • false → disabled
  • object → enabled with configuration

Common options:

  • onError: 'warn' | 'throw' (default: 'throw')
  • ignore: string | RegExp | Array<string | RegExp> (default: []) If the stack trace or emitted message matches, the leak is ignored.

possible report options

  • timers: track real timers
  • fakeTimers: track fake timers
  • promises: track not awaited promises
  • console: track console output
    • methods: Array<keyof Console> (default: all) which console methods should be tracked
  • processOutputs: track process output
    • methods: Array<'stderr' | 'stdout'> (default: both) which process output methods should be tracked

timerIsolation

Controls when timers are validated and cleared.

afterEach (default) beforeAll, beforeEach and afterAll are still immediate but test / it and afterEach block defer reporting and cleanup until the last afterEach block is executed (or directly after the test if there are no afterEach blocks).

beforeAll  → check
beforeEach → check
test       → defer
afterEach  → defer
afterEach  → final check
afterAll   → check

This allows easier cleanup., for example react testing framework registers an unmount function in an afterEach block to clean up. The disadvantage of this method is that it can happen that in an afterEach block a long-running task is executed and while running it timers resolve unnoticed.

immediate timers are checked after each test / hook block

beforeAll  → check
beforeEach → check
test       → check
afterEach  → check
afterAll   → check

Use when tests should clean up immediately.

delayThreshold

number (default: 0)

The delay in milliseconds of all setTimeout and setInterval callback that get executed is added up. If the sum is higher than the threshold, an error is thrown; otherwise a warning is logged. This feature should helps to detect tests that accidentally rely on real time.

clearTimers

boolean (default: true)

Whether timers should be cleared automatically based on timerIsolation.

verbose

boolean (default: false)

Jest often hides stack traces and files are not clickable. Also it is only possible to report one error type at a time. This option will print all errors with the related stack traces.


📊 Reporter

The reporter aggregates leaks across all test environments and prints:

  • Total number of leaks
  • Grouped by type (timers, promises, console, etc.)
  • Ordered by severity

The environment writes temporary reports to disk and the reporter reads them.

The reporter can be configured by the standard jest reporter config syntax

Options:

  • tmpDir: string (default: .tmp) Directory used to exchange data between environment and reporter.
export default {
  reporters: ['default', ['jest-doctor/reporter', { tmpDir: 'custom-dir' }]],
};

⚠️ Limitations

No it.concurrent

Concurrent tests cannot be isolated reliably. jest-doctor replaces them with a synchronous version to guarantee deterministic cleanup.

No done callbacks or generators

Since this is also a legacy pattern, it is not supported to avoid unnecessary complexity.

Results are inconsistent

Promises are handled differently depending on the OS and node version. This means the report will always look a bit different depending on the environment.

Microtasks resolving in same tick are not tracked

This is a JavaScript limitation, not specific to jest-doctor.

Promise.resolve().then(() => {
  /* i am not tracked as unresolved */
});

Concurrent promise combinators with nested async are problematic

Promise.race, Promise.any, Promise.all cannot safely untrack nested async:

const doSomething = async () => {
  // both promises will be tracked and never released
  await someAsyncTask();
  return new Promise(() => {
    setTimeout(resolve, 10);
  });
};

const p1 = Promise.resolve().then(() => {
  /* no problem if not async */
});

const p2 = Promise.resolve().then(
  () =>
    new Promise((resolve) => {
      /* this promise will be also always tracked */
      resolve();
    }),
);

await Promise.race([p1, p2, doSomething()]);

Imported timers bypass tracking

These timers are not intercepted. This can also be used as an escape hatch.

import { setTimeout, setInterval } from 'node:timers';

🚫 When not to use jest-doctor

  • Heavy integration tests with background workers
  • Tests relying on long-running real timers
  • Legacy test suites using callback-based async

In such cases, consider selectively disabling checks or using ignore rules.


💡 Recommendations

  • Use ESLint to
    • detect floating promises
    • disallow setTimeout / setInterval in tests
    • disallow console usage
  • Only mock console / process output per test not globally, to avoid missing out on errors that are thrown in silence
  • Enable fake timers globally in config (be aware that there might be some issues ie axe needs real timers)
afterEach(async () => {
  jest.useRealTimers();
  await axe();
  jest.useFakeTimers();
});

🧪 Tested Against

This project is tested against the following combinations:

  • jest: 28, 29, 30
  • node: 20, 22, 24

❓ FAQ

How to migrate an existing project?

Please read the migration guide.

Why is jest-doctor so strict?

Because flaky tests cost more than broken builds.

Does this slow tests down?

Slightly. Overhead is intentional and bounded.

What is an async leak?

An async leak happens when a test starts asynchronous work but does not properly wait for or clean it up. This can:

  • Interfere with later tests
  • Cause flaky failures
  • Hide real bugs

Why does console output fail tests?

In the best case it just pollutes the console. In the worst case a real bug is logged but ignored. Thats why tests should always spy on console and assert on the output. The react example shows a common problem that can be caught by tests that mock console correctly.


If jest-doctor helped you eliminate flaky tests, consider ⭐ starring the repo — it helps others discover the project and motivates continued development.