tape-six
v1.7.0
Published
The test harness for the modern JavaScript and TypeScript.
Maintainers
Readme
tape-six 
tape-six is a TAP-based library for unit tests.
It is written in the modern JavaScript for the modern JavaScript and works in Node, Deno, Bun and browsers.
It runs ES modules (import-based code) natively and supports CommonJS modules transparently using the built-in ESM.
It can run TypeScript code with modern versions of Node, Bun and Deno without transpilation. Obviously TS bindings are included.
Individual test files can be executed directly with node, deno, bun without a need for a special test runner utility. It facilitates debugging and improves testing.
Why tape-six? It was supposed to be named tape6 but npm does not allow names "similar"
to existing packages. Instead of eliminating name-squatting they force to use unintuitive and
unmemorable names. That's why all internal names, environment variables, and public names still use tape6.
See examples in the wiki.
Rationale
Why another library? Working on projects written in modern JS (with modules) I found several problems with existing unit test libraries:
- In my opinion unit test files should be directly executable with Node, Bun, Deno, browsers
(with a trivial HTML file to load a test file) without a need for a special test runner utility,
which wraps and changes my beautiful code.
- Debugging my tests should be trivial. It should not be different from debugging any regular file.
- The test harness should not obfuscate code nor include hundreds of other packages.
- I want to debug my code, not dependencies I've never heard about.
- I want to see where a problem happens, not some guts of a test harness.
- Tests should work with ES modules natively.
- What if I want to debug some CommonJS code with Node? No problem, it just works.
- Tests should work with TypeScript natively.
- It just works: modern runtimes (Node, Deno, Bun) support running TypeScript natively without transpilation by ignoring type information and running the code directly.
- The DX in browsers are usually abysmal.
- Both console-based debugging and a UI to navigate results are properly supported.
- Integration with browser automation tools is supported for automated testing.
- Examples for such tools are Playwright and Puppeteer are provided.
How it looks
(The examples below show actual output of test functions. In real life, successful tests are usually hidden and only the final results are shown. Usually failed tests are shown with details and stack traces and the first fail stops testing to speed up the fail-fix cycle. All those details can be configured with settings.)
Running a test file directly:
$ node tests/test-console.js
○ console test
log: log #1
✓ pass - 1.542ms
log: log #2
err: error #1
log: log #2a
✓ should be truthy - 1.725ms
log: log #3
err: error #2
✓ console test 2 0 - 4.747ms
♥️ tests: 1, asserts: 2, passed: 2, failed: 0, skipped: 0, todo: 0, time: 6.474msRunning a test suite:
$ npx tape6 tests/test-console.js tests/test-eval.js
○ FILE: /tests/test-console.js
○ console test
log: log #1
✓ pass - 0.338ms
log: log #2
err: error #1
log: log #2a
✓ should be truthy - 0.146ms
log: log #3
err: error #2
✓ console test 2 0 - 1.286ms
✓ FILE: /tests/test-console.js 2 0 - 7.411ms
○ FILE: /tests/test-eval.js
○ OK test
✓ 1 < 2 - 1.28ms
✓ a < b - 0.17ms
✓ a + b + c == "3three" - 0.13ms
✓ d.a < d.b - 0.105ms
✓ OK test 4 0 - 2.695ms
○ OK test with self
✓ internal check - 0.178ms
✓ 1 < 2 - 0.115ms
✓ OK test with self 2 0 - 0.55ms
✓ FILE: /tests/test-eval.js 6 0 - 5.756ms
♥️ tests: 5, asserts: 8, passed: 8, failed: 0, skipped: 0, todo: 0, time: 108.8msMore colorful versions (click to see the original screenshots):
And:
Docs
The documentation can be found in the wiki. See how it can be used in tests/.
The whole API is based on two objects: test and Tester.
test
test is the entry point to the test suite:
import test from 'tape-six';
// import {test} from 'tape-six';
// CommonJS:
// const {test} = require('tape-six');
// const {default: test} = require('tape-six');This function registers a test suite. Available options:
async test(name, options, testFn)— registers a test suite to be executed asynchronously. The returned promise is resolved when the test suite is finished.- In most cases no need to wait for the returned promise.
- The test function has the following signature:
async testFn(tester)- The function can be synchronous or asynchronous.
async test.skip(name, options, testFn)— registers a test suite to be skipped.- It is used to mark a test suite to be skipped. It will not be executed.
async test.todo(name, options, testFn)— registers a test suite that is marked as work in progress.- Tests in this suite will be executed, errors will be reported but not counted as failures.
- It is used to mark tests for incomplete features under development.
async test.asPromise(name, options, testPromiseFn)— registers a test suite to be executed asynchronously using the callback-style API to notify that the test suite is finished.- The test function has a different signature:
testPromiseFn(tester, resolve, reject).
- The test function has a different signature:
The arguments mentioned above are:
name— the optional name of the test suite. If not provided, it will be set to the name of the test function or'(anonymous)'.options— the optional options object. Available options:skip— iftrue, the test suite will be skipped.todo— iftrue, the test suite will be marked as work in progress.name— the optional name of the test suite. If not provided, it will be set to the name of the test function or'(anonymous)'.- Can be overridden by the
nameargument.
- Can be overridden by the
timeout— the optional timeout in milliseconds. It is used for asynchronous tests.- If the timeout is exceeded, the test suite will be marked as failed.
- Important: JavaScript does not provide a generic way to cancel asynchronous operations.
When the timeout is exceeded,
tape6will stop waiting for the test to finish, but it will continue running in the background. - The default: no timeout.
testFn— the optional test function to be executed (see below).- Can be overridden by the
testFnargument.
- Can be overridden by the
testPromiseFn— the optional callback-based test function to be executed.- Can be overridden by the
testPromiseFnargument.
- Can be overridden by the
testFn— a test function to be executed. It will be called with thetesterobject. The result will be ignored.- This function can be synchronous or asynchronous.
testPromiseFn— a callback-based test function to be executed (see below). It will be called with thetesterobject and two callbacks:resolveandrejectmodeled on the Promise API.- Value supplied to
resolve()will be ignored. - Value supplied to
reject()will be used as the error message.
- Value supplied to
Given all that test and its helpers can be called like this:
test(name, options, testFn);
test(name, testFn);
test(testFn);
test(name, options);
test(options, testFn);
test(options);
// examples:
test('foo', t => {
t.pass();
});
test('bar', async t => {
t.fail();
});
test(function baz(t) {
t.ok(1 < 2);
});
test({
name: 'qux',
todo: true,
testFn: t => {
t.ok(1 < 2);
}
});Examples of callback-based tests:
test.asPromise(name, options, testPromiseFn);
test.asPromise(name, testPromiseFn);
test.asPromise(testPromiseFn);
test.asPromise(name, options);
test.asPromise(options, testPromiseFn);
test.asPromise(options);
// examples:
test.asPromise('foo', (t, resolve, reject) => {
t.pass();
const result = someAsyncOperationAsPromise();
result.then(resolve).catch(reject);
});
test.asPromise('bar', async (t, resolve, reject) => {
const nodeStream = fs.createWriteStream('bar.txt');
nodeStream.on('error', reject);
nodeStream.on('finish', resolve);
nodeStream.write('hello');
nodeStream.end();
});Tester
Tester helps to do asserts and provides an interface between a test suite and the test harness.
The following methods are available (all msg arguments are optional):
- Asserts:
pass(msg)— asserts that the test passed.fail(msg)— asserts that the test failed.ok(val, msg)— asserts thatvalis truthy.true()— an alias ofok().assert()— an alias ofok().
notOk(val, msg)— asserts thatvalis falsy.false()— an alias ofnotOk().notok()— an alias ofnotOk().
error(err, msg)— asserts thaterris falsy.ifError()— an alias oferror().ifErr()— an alias oferror().iferror()— an alias oferror().
strictEqual(a, b, msg)— asserts thataandbare strictly equal.- Strict equality is defined as
a === b. is()— an alias ofstrictEqual().equal()— an alias ofstrictEqual().isEqual()— an alias ofstrictEqual().equals()— an alias ofstrictEqual().strictEquals()— an alias ofstrictEqual().
- Strict equality is defined as
notStrictEqual(a, b, msg)— asserts thataandbare not strictly equal.not()— an alias ofnotStrictEqual().notEqual()— an alias ofnotStrictEqual().notEquals()— an alias ofnotStrictEqual().notStrictEquals()— an alias ofnotStrictEqual().doesNotEqual()— an alias ofnotStrictEqual().isUnequal()— an alias ofnotStrictEqual().
looseEqual(a, b, msg)— asserts thataandbare loosely equal.- Loose equality is defined as
a == b. looseEquals()— an alias oflooseEqual().
- Loose equality is defined as
notLooseEqual(a, b, msg)— asserts thataandbare not loosely equal.notLooseEquals()— an alias ofnotLooseEqual().
deepEqual(a, b, msg)— asserts thataandbare deeply equal.- Individual components of
aandbare compared recursively using the strict equality. - See deep6's equal() for details.
same()— an alias ofdeepEqual().deepEquals()— an alias ofdeepEqual().isEquivalent()— an alias ofdeepEqual().
- Individual components of
notDeepEqual(a, b, msg)— asserts thataandbare not deeply equal.notSame()— an alias ofnotDeepEqual().notDeepEquals()— an alias ofnotDeepEqual().notEquivalent()— an alias ofnotDeepEqual().notDeeply()— an alias ofnotDeepEqual().isNotDeepEqual()— an alias ofnotDeepEqual().isNotEquivalent()— an alias ofnotDeepEqual().
deepLooseEqual(a, b, msg)— asserts thataandbare deeply loosely equal.- Individual components of
aandbare compared recursively using the loose equality.
- Individual components of
notDeepLooseEqual(a, b, msg)— asserts thataandbare not deeply loosely equal.throws(fn, msg)— asserts thatfnthrows.fnis called with no arguments in the global context.
doesNotThrow(fn, msg)— asserts thatfndoes not throw.matchString(string, regexp, msg)— asserts thatstringmatchesregexp.doesNotMatchString(string, regexp, msg)— asserts thatstringdoes not matchregexp.match(a, b, msg)— asserts thatamatchesb.- See deep6's match() for details.
doesNotMatch(a, b, msg)— asserts thatadoes not matchb.rejects(promise, msg)— asserts thatpromiserejects.- This is an asynchronous method. It is likely to be waited for.
doesNotResolve()— an alias ofrejects().
resolves(promise, msg)— asserts thatpromiseresolves.- This is an asynchronous method. It is likely to be waited for.
doesNotReject()— an alias ofresolves().
- Embedded test suites (all of them are asynchronous and should be waited for):
test(name, options, testFn)— runs a test suite asynchronously. Seetest()above.skip(name, options, testFn)— skips a test suite asynchronously. Seetest.skip()above.todo(name, options, testFn)— runs a provisional test suite asynchronously. Seetest.todo()above.asPromise(name, options, testPromiseFn)— runs a test suite asynchronously. Seetest.asPromise()above.
- Miscellaneous:
any— returns theanyobject. It can be used in deep equivalency asserts to match any value. See deep6's any for details.plan(n)— sets the number of tests in the test suite. Rarely used.comment(msg)— sends a comment to the test harness. Rarely used.skipTest(...args, msg)— skips the current test yet sends a message to the test harness.bailOut(msg)— stops the test suite and sends a message to the test harness.
- Evaluators:
OK(condition, msg, options)— a high-level helper for evaluating simple expressions.- Available since 1.4.0.
- See Tester for description and examples.
In all cases, the msg message is optional. If it is not provided, some suitable generic message will be used.
Example:
test('Sample test', async t => {
const result = await getFromDb({first: 'Bob', last: 'Smith'});
t.equal(result.position, 'chief bozo', 'the position is correct');
t.ok(result.manager, 'the manager exists');
const manager = await getFromDb(result.manager);
t.ok(manager, 'the manager is retrieved');
t.equal(manager.first, 'Jane', 'the manager is Jane');
t.deepEqual(manager.employees, ['Bob Smith'], 'Jane manages only Bob Smith');
});Before/after hooks
tape-six supports scope-based before/after hooks: beforeAll, afterAll, beforeEach, afterEach, which can be used to set-up and tear-down a proper environment for tests. Read all about it in before and after hooks.
Running tests
It is super easy to run tests:
- Install the
tape-sixpackage:npm i -D tape-six - Write a test. For example, you named it
test.js. - Run the test:
node test.js- Or:
bun run test.js - Or:
deno run -A test.js(you can use appropriate permissions). - Or you can run them in a browser!
- Or:
- Profit!
If you have a lot of tests, you can organize them using multiple files and directories (see configuration below).
tape-six provides multiple test runners that can run them in different environments.
Tests can run in parallel using multiple threads to speed up the whole process.
tape6 # run tests in parallel using all available threads
tape6 --par 4 # run tests in parallel using 4 threads
tape6 --par 1 # run one test at a timeIf you want to run tests in separate processes, check out tape-six-proc. Why do you want to do that? When tests have to modify globals or use single-threaded binary extensions.
If you want to run tests sequentially without expenses of threads and processes, you can use the tape6-seq command, which uses the same options and configuration as tape6:
tape6-seqConfiguring test runners
TLDR version — add to your package.json:
{
// ...
"scripts": {
"test": "tape6 --flags FO",
"start": "tape6-server --trace"
}
// ...
"tape6": {
"tests": ["/tests/test-*.*js"],
"importmap": {
"imports": {
"tape-six": "/node_modules/tape-six/index.js",
"tape-six/": "/node_modules/tape-six/src/",
"my-package": "/index.js",
"my-package/": "/src/"
}
}
}
}See set-up tests for details.
Command-line utilities
- tape6 — the main utility of the package to run tests in different environments.
- tape6-server — a custom web server with a web application that helps running tests in browsers.
Test output can be controlled by flags. See Supported flags for details.
Release notes
The most recent releases:
- 1.7.0 New features: after/before hooks for tests, aliases for
suite(),describe(),it(),tape6-seq— an in-process sequential test runner. Improvements: stricter monochrome detection, refactoring, bugfixes, updated dev dependencies and the documentation. - 1.6.0 New features: support for
AssertionErrorand 3rd-party assertion libraries based on it likenode:assertandchai, support forconsole.assert(), support forsignalto cancel asynchronous operations, tests wait for embedded tests, improved reporting of errors, updated dev dependencies. - 1.5.1 Better support for stopping parallel tests, better support for "failed to load" errors.
- 1.5.0 Internal refactoring (moved state to reporters), added type identification of values in the DOM and TTY reporters, multiple minor fixes.
For more info consult full release notes.
