CLI tool
tapjs/tapjs avatar
tapjs/tapjs

tapjs/tapjs: TAP test runner and tooling for Node.js

Test Anything Protocol tools for node

2,426 stars283 forksJavaScriptNOASSERTION

At a glance

What is it?
tapjs is the monorepo behind node-tap, a test runner built around the Test Anything Protocol. It ships a plugin-based Test class, a CLI runner, and a parser, and it is aimed at Node.js projects that want TAP output and code coverage from the same tool.
Who is it for?
Adopt tapjs if you are testing a Node.js project and want TAP output, a plugin-extensible Test class, and coverage tracking from one runner. Do not adopt it if your stack is outside Node, or if you need a browser runner, since the repository only covers Node tooling.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 29 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What tapjs solves, and who it is written for

The repository describes itself as a workspace for node-tap development, and the packages inside it are the pieces of a test runner: a parser for the Test Anything Protocol, a core library of the runner's moving parts, a plugin-ified Test class, a CLI runner, a config layer, assertion methods, and a set of optional plugins. The problem it addresses is narrow and specific. If you write tests for Node.js code and you want the results expressed as TAP, you need both a runner that produces that stream and a parser that can read it back, and tapjs ships both in one workspace.

The audience is Node.js developers, and the topics attached to the repository (assert, tdd, bdd, commonjs, esm, typescript, code-coverage) describe the shape of that audience rather than a general testing crowd. The Test class carries assertions such as t.equal() and t.match() through the @tapjs/asserts plugin, so a project can write tests in the same style as a classic assertion library. TypeScript support arrives through the @tapjs/typescript plugin, which the README lists as a default plugin and which adds a --typecheck option. Teams that already emit TAP from another language can use tap-parser on its own, since it is published as a separate module in the workspace.

What it is not is a browser test runner or a general-purpose harness for non-Node runtimes. Nothing in the repository layout points at a browser environment. The plugins listed are all Node-side concerns: spawning processes, reading stdin, mocking module requires, intercepting calls, faking clocks, and building fixture directories.

How the plugin architecture actually fits together

The central mechanism is the plugin-ified Test class in @tapjs/test. The README explains the reason for the split: there is a bootstrapping cycle between @tapjs/core, @tapjs/test, and all of the plugins, and because of that cycle those packages MUST use skipLibCheck: true in their tsconfigs. The README adds that it should not be used in other packages. That constraint is the clearest single fact about the architecture. The type surface of the Test class is assembled from plugins, and the type checker cannot fully resolve that assembly, so the project disables library checking for exactly the packages caught in the cycle.

A consequence is the bootstrap step. The README says to run npm run bootstrap at least once to build the @tapjs/test module with the default set of plugins so that the other libraries can build properly, and warns that npm install will not work until you do this, because the generated TypeScript eats its own tail. For anyone consuming tap from npm this is invisible. For anyone working in the repository itself, it is the first thing that must happen.

Default plugins extend the Test object with methods: t.before(), t.beforeEach(), t.after() and t.teardown(), t.afterEach(), t.spawn(), t.stdin(), the assertion methods, t.matchSnapshot(), t.testdir(), t.mockRequire() and t.mockImport(), t.intercept() and t.capture(), and t.only() together with the --grep and --only CLI options. Optional plugins add t.nock(), a clock-mock object on t.clock, a Sinon sandbox on t.sinon, and an alternative TypeScript loader through @tapjs/esbuild-kit that uses the esbuild-kit loaders instead of ts-node. The README also points at @tapjs/sinon as the place to go if the built-in intercept and capture are not enough, describing them as a scaled-down minimal form of Sinon.

@tapjs/processinfo, which handles process information and code coverage, lives outside the monorepo. The README gives the reason: it cannot be tested by a version of tap that uses itself without bootstrap paradoxes. That is a design decision worth noting, because it means coverage tracking is one of the few parts of the stack you will not find in this repository's src directory.

Installing tap and running a first test

The repository README is written for contributors and does not give consumer install steps; the package is published to npm and the project's homepage is node-tap.org, which is where the user-facing documentation lives. The commands below are the contributor workflow the README documents, and they are what you run if you clone the repository rather than install the published package.

Start with the bootstrap step. The README states that npm install will not work until it has been run, because the generated TypeScript eats its own tail.

bash
npm run bootstrap

After any change to a plugin or to the core, the test class has to be rebuilt. The README marks this as required:

bash
npm run build

For a build of a single workspace, the README gives a scoped form, where {whatever} is the workspace directory name under src:

bash
npm run prepare -w src/{whatever}

After adding or removing workspaces, the lockfile has to be refreshed before anything else will resolve:

bash
npm i

Running the test suite across every workspace is a single command. A variant, npm run snap, does the same but saves snapshots, which is what you use when snapshot output has legitimately changed.

bash
npm test
bash
npm run snap

To build and serve the documentation site, the README gives npm start. The repository contains the Eleventy configuration and assets for that site (typedoc.json, typedoc.base.json, typedoc.css, and the .11ty dependencies in the root package.json), so this is the docs build for the project itself, not a step you need in order to use tap in your own code.

Where tapjs is the wrong tool

The repository is Node-only in every part the README describes. The plugins deal with module require and import mocking, process spawning, stdin, fixture directories, and TypeScript loading. None of that maps onto a browser. If your test suite needs to drive a DOM or a real browser engine, this is not the project for it, and the plugin list does not suggest a path to one.

The bootstrap constraint is a second boundary, this time for contributors rather than users. The README says the cycle between @tapjs/core, @tapjs/test, and the plugins forces skipLibCheck: true in their tsconfigs, and that it should not be used in other packages. If you are working inside the monorepo and you want full library type checking across the whole build, that is not the arrangement this repository offers. You get it everywhere except the packages in the cycle.

The plugin model also means the Test class is not a fixed surface. Methods like t.matchSnapshot() or t.mockImport() exist because a plugin adds them. If you disable or omit a plugin, the method is not there, and the README's framing of some plugins as default and others as optional tells you which methods you can count on. The optional ones (nock, clock, sinon, esbuild-kit) are add-ons you install deliberately.

Finally, the repository metadata reports the licence as NOASSERTION. The repository contains a LICENSE.md file, but the metadata does not resolve to a named licence. If licence terms matter to your organisation, read LICENSE.md in the repository rather than trusting the metadata field.

tapjs compared with node:test

The most direct alternative for a Node.js project is the built-in node:test module, which requires no dependency at all and is part of the runtime. The difference in approach is where the runner comes from. With node:test you get whatever the runtime ships, and you upgrade the runner by upgrading Node. With tapjs you install a runner and get a plugin architecture you can extend: the Test class is assembled from @tapjs/core and a set of plugins, and you can add behaviour to t by writing a plugin, which is what @tapjs/create-plugin exists to facilitate through npm init @tapjs/plugin.

That extensibility cuts both ways. node:test has no plugin surface to learn and no bootstrap cycle to work around, while tapjs asks you to understand which default plugins are active and which optional ones you have added. The trade you are making is roughly: fewer moving parts against a Test object whose methods you can change.

If you want a Sinon-style sandbox, tapjs offers it through @tapjs/sinon at t.sinon, and a lighter version through @tapjs/intercept at t.intercept() and t.capture(). That is a concrete capability difference rather than a stylistic one. The README is explicit that the built-in intercept and capture are a scaled-down minimal form of Sinon, so if you need the full library you install the plugin rather than expecting the core to grow into it.

Maintenance, releases and upgrade cost

The repository is not archived, and the last push was on 2026-09-03. The most recent release listed is [email protected], dated 2026-09-03, with [email protected] on 2026-07-27 and [email protected] on 2026-05-15 before it. Release cadence over that window is uneven: two releases in the two months before the latest, and a gap of roughly two and a half months before that. Nothing in the repository describes a long-term support policy or a deprecation schedule for older tap majors, so plan upgrades as ordinary version bumps and check the changelog.

The upgrade cost is concentrated in two places. The first is the plugin set. Because the Test class is built from plugins, a change to a default plugin changes the methods available on t, and the README treats the default list as a defined set. The second is the TypeScript loader. The default path is ts-node, and @tapjs/esbuild-kit exists as an optional alternative that loads TypeScript through the esbuild-kit cjs and esm loaders instead. Switching between them is a configuration change, not a rewrite, but it is a decision you make once and then carry.

The root package.json declares the workspace as private and lists a large devDependency set, including nx for the build graph, Eleventy for the docs, and c8 for coverage. None of that ships to consumers of the published tap package. It does mean that contributing to the repository requires installing a substantial toolchain, and that the bootstrap step is not optional before any of it will run.

On licensing, the metadata says NOASSERTION, and the repository contains LICENSE.md. That file is the authoritative source for terms, and it is the one to read before you depend on the project commercially. Nothing here should be read as legal advice.

Editorial conclusion

Adopt tapjs if you are testing a Node.js project and want TAP output, a plugin-extensible Test class, and coverage tracking from one runner. Do not adopt it if your stack is outside Node, or if you need a browser runner, since the repository only covers Node tooling. Before committing, verify that the tap major version you install matches the Node version you run, check which default plugins you are relying on, and read LICENSE.md yourself, because the repository metadata reports the licence as NOASSERTION rather than naming one.

Frequently asked questions

What is the TAP protocol that tapjs implements?

TAP stands for Test Anything Protocol, and tapjs ships a parser for it in the tap-parser workspace module as well as a runner that produces it. The README describes tap-parser simply as the module that parses TAP, and links to testanything.org for the protocol itself.

How do I install tapjs for my own project?

The repository README documents the contributor workflow rather than consumer install steps, and the project's homepage is node-tap.org, which is where the user-facing documentation lives. Inside the repository, npm install will not work until npm run bootstrap has been run once.

What does the @tapjs/mock plugin add to tapjs?

The README lists @tapjs/mock as a default plugin that adds t.mockRequire() and t.mockImport(). Both methods are available on the Test class once the default plugin set is built.

Can I use tapjs to test TypeScript files?

Yes. @tapjs/typescript is a default plugin that adds TypeScript support and the --typecheck config option, and the default loader is ts-node. An optional plugin, @tapjs/esbuild-kit, loads TypeScript through the esbuild-kit cjs and esm loaders instead.

Why does tapjs require skipLibCheck in some tsconfigs?

The README states there is a bootstrapping cycle between @tapjs/core, @tapjs/test, and all of the plugins, so those packages MUST use skipLibCheck: true. It adds that the option should not be used in other packages.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. tapjs/tapjs on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tapjs-tapjs.svg)](https://hysenlabs.com/projects/tapjs-tapjs)