Node-TAP Test Framework
TAP (Test-Anything-Protocol) is a robust and opinionated test framework for Node.js, currently in version 21.7.1. It provides a comprehensive command-line test runner and a JavaScript/TypeScript framework for writing tests that output in the TAP format. A key differentiator is its philosophy of treating test files as "normal" programs, running each test in its own process to prevent state leakage and inter-test dependencies. Since version 18, `tap` has been entirely rewritten in TypeScript, offering first-class support for ESM, CommonJS, and TypeScript out-of-the-box, including rich, machine-generated type definitions for an enhanced developer experience with editor auto-completion. It features built-in test coverage (powered by `c8`), various reporter formats, and an extensive API leveraging a plugin-based architecture for core functionalities like assertions and mocking. The project maintains an active release cadence with frequent updates.
Common errors
-
Error: The current Node.js version (X.Y.Z) is not supported. Please upgrade to Node.js 20 or newer.
cause The installed Node.js version is below the minimum required by `tap` (20 or 22).fixUpdate your Node.js environment to version 20 or 22. Use `nvm install 20 && nvm use 20` or similar commands for your Node.js version manager. -
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ... require() of ES modules is not supported.
cause Attempting to `require()` an ES Module or running a CommonJS file that imports an ESM-only dependency, particularly in projects that haven't fully migrated to ESM or correctly configured hybrid modules.fixEnsure `import` statements are used for ES Modules. If running a CommonJS test file, either rewrite it to ESM or ensure all its dependencies are also CommonJS or correctly hybrid-compatible. For `.js` files that are ESM, add `"type": "module"` to your `package.json` or use `.mjs` extension. -
AssertionError: Values are not deeply equal
cause A `t.deepEqual()` (or similar assertion) failed because the actual value does not strictly match the expected value, often due to differences in object structure or primitive values.fixInspect the assertion's `diff` output to understand the discrepancy between the actual and expected values. Adjust either your assertion's expected value or the code under test. -
Test suite hangs, never finishes, or passes without running assertions.
cause An asynchronous test or subtest did not explicitly signal its completion via `t.end()` or by returning a Promise. Or `t.plan()` was used with an incorrect count.fixFor asynchronous tests, ensure the test callback is an `async` function (which implicitly returns a Promise) or explicitly call `t.end()` when all asynchronous operations are complete. If `t.plan(N)` is used, verify that exactly N assertions are run.
Warnings
- breaking Node.js version 20 or >=22 is now required. Older Node.js versions (e.g., Node.js 18 or 16) are no longer supported and will cause `tap` to fail at runtime.
- breaking Starting with `tap` v18, the module system was rewritten to be a hybrid ESM/CommonJS package. Projects upgrading from versions prior to v18 might encounter `ERR_REQUIRE_ESM` or similar module resolution errors if they incorrectly mix `require()` with ESM modules.
- breaking As of `tap` v18, code coverage is enabled by default, and a 100% coverage threshold is enforced by default. Tests will fail if code is not fully covered or if coverage reports are missing.
- deprecated Assertion synonyms (e.g., `t.is_not_equal()`) were deprecated in v16 and largely removed in v18. Using these older assertion names will result in errors.
- gotcha While `tap` fully supports TypeScript, enabling strict type checking (`typecheck: true`) can significantly increase test run times, especially in large projects with many test files, potentially adding 500-750ms per file.
Install
-
npm install tap -
yarn add tap -
pnpm add tap
Imports
- tap
const tap = require('tap')import tap from 'tap'
- test
import { tap } from 'tap'import { test } from 'tap' - Test
import { Test } from 'tap'import type { Test } from 'tap'
Quickstart
import tap from 'tap';
import type { Test } from 'tap';
interface MyApiResult {
data: string;
status: number;
}
async function fetchData(): Promise<MyApiResult> {
// Simulate an API call
return new Promise(resolve => {
setTimeout(() => {
resolve({ data: 'Hello TAP!', status: 200 });
}, 50);
});
}
tap.test('Basic assertions and async test', async (t: Test) => {
t.plan(3); // Declare the number of assertions expected
t.ok(true, 'should be truthy');
t.equal(1 + 1, 2, 'addition works');
const result = await fetchData();
t.deepEqual(result, { data: 'Hello TAP!', status: 200 }, 'async data matches expected structure');
});
tap.test('Skipping a test', (t: Test) => {
t.skip('This test is intentionally skipped for now');
t.end(); // Must call t.end() for non-async tests unless t.plan() is used.
});
tap.test('Synchronous operations', (t: Test) => {
const arr = [1, 2, 3];
t.notHas(arr, 4, 'array should not contain 4');
t.throws(() => {
throw new Error('Expected error');
}, 'Expected error was thrown');
t.end();
});