TAP (Test Anything Protocol) Node.js Test Runner

repository·main·Indexed 11 days ago

https://github.com/tapjs/node-tap

A modular test runner ecosystem built on the Test Anything Protocol (TAP) featuring a plugin-based architecture. It provides core testing capabilities and specialized utilities through plugins such as @tapjs/asserts for assertions, @tapjs/snapshot for snapshot testing, @tapjs/mock for mocking, and lifecycle hooks like t.before(), t.after(), t.beforeEach(), and t.afterEach().

Tokens
73.2K
Snippets
269
Records
386
Agent score
79%

What's inside TAP

  1. Overview of node-tap features

    main

    node-tap is an extensible Test Anything Protocol (TAP) test framework. It provides a comprehensive suite of testing tools out of the box, including:

    • TypeScript support: Native support for TypeScript testing.
    • CLI: A command-line interface for running tests.
    • Assertions: A wide variety of assertion methods.
    • Snapshots: Support for snapshot testing.
    • Spies & Mocks: Object/method spying and module mocking capabilities.
    • Fixtures: File system fixtures for managing test data.
    • Lifecycle Hooks: Support for setup and teardown hooks.
    • Test Filtering: Ability to filter which tests are executed.
    • Code Coverage: Comprehensive analysis of code coverage.
    • Reporters: Color-accessible test reporters for various output formats.
  2. Overview of node-tap

    main

    node-tap is a Test Anything Protocol (TAP) test framework for Node.js. It consists of two main parts:

    1. A command line test runner for consuming TAP-generating test scripts.
    2. A JavaScript framework for writing the test scripts themselves.

    Key features include:

    • Support for TypeScript, ESM, and CommonJS out of the box.
    • A powerful plugin system for extending functionality (assertions, mocking, snapshots, etc.).
    • Built-in test coverage using V8's internal mechanisms via c8.
    • High-signal, low-noise reporting with accessibility considerations (colorblind friendly).
    • Test files are treated as "normal" programs that can be run directly in a standard Node.js environment.
  3. Use @tapjs/esbuild-kit as an alternative to @tapjs/typescript

    main
    If you want to load TypeScript tests using esbuild loaders instead of ts-node, use @tapjs/esbuild-kit. This package utilizes @esbuild-kit/cjs-loader and @esbuild-kit/esm-loader to handle TypeScript files, providing an alternative to the standard @tapjs/typescript plugin.
  4. Overview of @tapjs core modules and plugins

    main

    The @tapjs ecosystem is a modular test runner architecture. The main entry point is tap, which sets up the root test runner. Most functionality is provided via plugins that extend the Test class (the plugin-ified version of the core Test class).

    Default Plugins

    These are typically included to provide standard testing capabilities:

    • @tapjs/typescript: Adds TypeScript support and the --typecheck config option.
    • @tapjs/asserts: Adds assertion methods like t.equal() and t.match().
    • @tapjs/before / @tapjs/before-each: Adds t.before() and t.beforeEach().
    • @tapjs/after / @tapjs/after-each: Adds t.after(), t.teardown(), and t.afterEach().
    • @tapjs/snapshot: Adds t.matchSnapshot().
    • @tapjs/filter: Adds t.only() and support for --grep and --only CLI options.
    • @tapjs/mock: Adds t.mockRequire() and t.mockImport().
    • @tapjs/intercept: Adds t.intercept() and t.capture().

    Optional Plugins

    • @tapjs/sinon: Provides a Sinon sandbox at t.sinon that auto-restores.
    • @tapjs/nock: Provides t.nock().
    • @tapjs/clock: Provides a clock-mock object on t.clock.
    • @tapjs/esbuild-kit: Uses esbuild loaders for TypeScript instead of ts-node.
  5. Compare tap runner and node --test runner behaviors

    main

    While both runners are interoperable, they exhibit different reporting behaviors:

    Using the tap runner

    • node:test tests: Reporting is limited to the test block and the first failure. node:test does not provide per-assertion reporting, so you won't see individual assertion failures within a block.
    • tap tests: Full reporting with per-assertion details.

    Using the node --test runner

    • tap tests: Provides diffs and source callsite printing.
    • node:test tests: Shows a console.log() of the thrown Error.

    Performance and Features

    • tap runner: Offers more features like automatic TypeScript support (without explicit --loader or --import arguments) and t.mockImport. However, it has higher overhead due to the feature set.
    • node --test runner: Generally faster as it has less overhead, but requires more manual configuration for TypeScript or advanced mocking.
  6. How loaders work in tap

    main

    Loaders are required when your tests use features like TypeScript, import mocking, or code coverage. When running tests via the tap CLI, these loaders are automatically injected. If you need to run them manually using the standard node command, you must specify them using the --loader Node.js argument.

    Common loaders include:

    • TypeScript: Provided by @tapjs/typescript via --loader ts-node/esm (or @tapjs/esbuild-kit).
    • Import Mocking: Provided by @tapjs/mock via --loader @tapjs/mock/loader.
    • Code Coverage: Built into tap core via --loader @tapjs/processinfo/loader.
    node --loader ts-node/esm test.ts
  7. Understand timing and reporting behavior in @tapjs/node-serialize

    main

    Because node:test and tap handle asynchronous testing differently, @tapjs/node-serialize uses a buffering strategy to ensure compatibility:

    1. The Problem: In tap, tests (especially subtests) can run concurrently or out of order relative to siblings when t.jobs > 1. However, node:test expects a strict, queued execution where tests are de-queued level by level.
    2. The Solution: The serializer builds a tree of subtests and waits to emit the relevant node:test event messages all at once in the specific order required by node --test.
    3. The Side Effect: You may observe that tests appear to 'hang' without output, and then suddenly emit all their data at once.

    Note: If you require real-time, in-progress reporting, you should use the standard tap test runner instead of the Node.js test runner.

  8. Important: Re-entry safety in capture()

    main

    The capture() method is not re-entry safe because it temporarily overrides the global Error.prepareStackTrace.

    If a capture() call is triggered while the library is already processing a stack (for example, if a method used by the CallSiteLike constructor like path.resolve() is intercepted), the library will detect the re-entry and return an empty array [] to prevent infinite recursion and stack overflow.

  9. Map CLI configuration to environment variables

    main

    All tap configuration values can be set via environment variables. To do this, transform the configuration key to uppercase and replace hyphens (-) with underscores (_).

    For example, the --debug flag corresponds to the TAP_DEBUG environment variable. Setting TAP_DEBUG=1 tap ./test.js is equivalent to running tap --debug ./test.js.

  10. Use coverage data in .tap/coverage

    main

    The .tap/coverage folder contains V8 coverage dump files named <uuid>.json, corresponding to the processes in .tap/processinfo. These files include a source-map-cache to enable line/column reporting.

    You can use this folder as an argument for other coverage tools or place your own coverage files here for tap to include in its reporting.