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

@zemd/testing

v1.1.0

Published

Focused, dependency-free helpers for native Node.js testing and benchmarking

Readme

@zemd/testing

npm

Focused, dependency-free helpers for small gaps in Node.js 24's native testing and performance APIs. The package is ESM-only and requires Node.js 24 or newer.

Installation

npm install --save-dev @zemd/testing

Promise rejections

getRejection guarantees that a promise rejected and returns its reason for detailed assertions:

import assert from "node:assert/strict";
import { getRejection } from "@zemd/testing";

const error = await getRejection(request());
assert.ok(error instanceof Error);
assert.deepStrictEqual(error.cause, { status: 503 });

Mock implementation sequences

Repeated calls to native mockImplementationOnce() target the same next call unless an invocation index is provided. mockImplementationSequence assigns consecutive implementations after any calls already recorded:

import assert from "node:assert/strict";
import { mock } from "node:test";
import { mockImplementationSequence } from "@zemd/testing";

const request = mock.fn(async () => "default");
mockImplementationSequence(request, [async () => "first", async () => "second"]);

assert.strictEqual(await request(), "first");
assert.strictEqual(await request(), "second");
assert.strictEqual(await request(), "default");

Environment variables

stubEnvironment applies a group of environment changes and registers idempotent restoration with context.after(). Multiple calls restore in LIFO order, including keys that did not exist before the test. Calling an older restore function manually first unwinds newer stubs registered on the same context, preserving that order.

import { test } from "node:test";
import { stubEnvironment } from "@zemd/testing";

test("uses a temporary endpoint", (context) => {
  stubEnvironment(context, { API_URL: "https://example.test" });
  // API_URL is restored automatically after the test.
});

Environment variables are process-wide state. Do not mutate the same keys from concurrent tests or subtests.

Mock timers

advanceTimersByTime performs a bounded number of promise microtask turns before and after MockTimers.tick(). The default is one turn; pass the optional third argument when the code under test has a known deeper promise chain.

context.mock.timers.enable({ apis: ["setTimeout"] });
const result = retryRequest();

await advanceTimersByTime(context.mock.timers, 100, 2);
await result;

The helper does not attempt to detect when the microtask queue is empty. Keeping the bound explicit avoids nondeterministic or unbounded flushing.

Synchronous benchmarks

benchmark measures a synchronous task in repeated batches with node:perf_hooks. It performs an unmeasured warmup and reports mean, median, minimum, and maximum nanoseconds per task invocation. Operations per second are calculated from the mean. If a warmup or measured invocation returns a Promise or another thenable, benchmark throws a TypeError instead of reporting incomplete timings.

import { benchmark, formatBenchmarkResult } from "@zemd/testing";

const input = '{"status":"ok"}';
let checksum = 0;
const result = benchmark(
  "JSON.parse",
  () => {
    const parsed = JSON.parse(input) as { status: string };
    checksum += parsed.status.length;
  },
  {
    iterations: 10_000,
    budgetNanoseconds: 1_000,
  },
);

if (checksum === 0) throw new Error("unexpected empty result");

console.table([formatBenchmarkResult(result)]);

formatBenchmarkResult keeps raw measurements separate from display formatting. It selects an appropriate duration unit, renders large throughput values compactly, and reports the coefficient of variation across measured batches. Lower variation means the batches were more consistent; a single-sample result reports n/a because variation cannot be estimated.

toBencherMetricFormat converts one or more raw results to Bencher Metric Format JSON. Give each package a stable namespace so benchmarks from different packages cannot collide and the same benchmark name keeps one history across releases:

import { toBencherMetricFormat } from "@zemd/testing";

const metrics = toBencherMetricFormat([result], {
  namespace: "@acme/parser",
});

process.stdout.write(`${JSON.stringify(metrics)}\n`);

The resulting latency value is the mean nanoseconds per operation and throughput is operations per second. Both values must be finite numbers so they remain valid BMF values after JSON serialization. The transformer performs no file or network I/O; benchmark scripts decide whether to print the readable table or persist BMF JSON for CI.

When budgetNanoseconds is set, the formatted result describes the mean relative to that informational budget:

  • within budget means the result has at least 10% headroom.
  • near budget means the result has less than 10% headroom without exceeding the budget.
  • over budget means the measured mean exceeded the budget.

The status does not throw or fail the benchmark. A budget is only meaningful when derived from the expected workload, and results should only be compared across equivalent runtime and machine conditions.

Each sample contains the configured number of iterations. The default is five samples and up to 1,000 warmup iterations. Use larger iteration counts for very fast tasks so timing the batch dominates the measurement overhead.

The task should consume its result or update an observable checksum when benchmarking otherwise-pure work. This prevents the runtime from discarding calculations whose results are unused.

The helper intentionally does not provide asynchronous measurement, automatic iteration calibration, result persistence, or performance thresholds. Benchmark timings vary by runtime and machine, so they should not be used as fixed-duration unit-test assertions.

For assertions, function spies, and existing object properties, prefer node:assert and the test context's native context.mock.method() or context.mock.property() APIs. context.mock.property() requires Node.js 24.3 or newer.

License

@zemd/testing is released under the Apache 2.0 license.

Donate