# tape-six > TAP-based unit test library for modern JavaScript and TypeScript. Works in Node, Deno, Bun, and browsers. ## Install npm i -D tape-six ## Quick start ```js import test from 'tape-six'; test('my test', async t => { t.ok(true, 'truthy value'); t.equal(1 + 1, 2, 'addition works'); t.deepEqual({a: 1}, {a: 1}, 'objects are equal'); }); ``` Run: `node my-test.js` or `npx tape6 --flags FO` ## API ### test(name, options, testFn) Registers a test. All three arguments are optional and can be in any order (recognized by type). - `name` (string) — test name. Defaults to function name or '(anonymous)'. - `options` (object) — see TestOptions below. - `testFn` (function) — `async (t: Tester) => void`. Can be sync or async. Returns a promise that resolves when the test finishes. Usually no need to await. Aliases: `suite`, `describe`, `it` — all are the same function. When called inside a test body, these (and all hook functions) automatically delegate to the current tester — no need to use `t.test()` or `t.before()` explicitly. ```js import {test, describe, it, suite} from 'tape-six'; ``` #### test.skip(name, options, testFn) Registers a test that will be skipped (not executed). #### test.todo(name, options, testFn) Registers a test marked as work-in-progress. Failures are reported but not counted. #### test.asPromise(name, options, testPromiseFn) Registers a test using callback-style completion: `(t, resolve, reject) => void`. ### TestOptions - `name` (string) — test name. - `testFn` (function) — test function. - `skip` (boolean) — skip this test. - `todo` (boolean) — mark as TODO. - `timeout` (number) — timeout in ms. Test is marked failed if exceeded. - `beforeAll` / `before` (function) — run before all embedded tests. - `afterAll` / `after` (function) — run after all embedded tests. - `beforeEach` (function) — run before each embedded test. - `afterEach` (function) — run after each embedded test. ### Tester (the `t` object) The object passed to test functions. Provides assertions and test control. #### Properties - `t.signal` — AbortSignal, aborted when test times out. Use for cancellation. - `t.any` (or `t._`) — symbol for matching any value in deepEqual/match. #### Assertions (all `msg` arguments are optional) - `t.pass(msg)` — unconditional pass. - `t.fail(msg)` — unconditional fail. - `t.ok(value, msg)` — assert truthy. Aliases: `true`, `assert`. - `t.notOk(value, msg)` — assert falsy. Aliases: `false`, `notok`. - `t.error(err, msg)` — assert err is falsy. Aliases: `ifError`, `ifErr`, `iferror`. - `t.strictEqual(a, b, msg)` — assert `a === b`. Aliases: `is`, `equal`, `equals`, `isEqual`, `strictEquals`. - `t.notStrictEqual(a, b, msg)` — assert `a !== b`. Aliases: `not`, `notEqual`, `notEquals`, `isNotEqual`, `doesNotEqual`, `isUnequal`, `notStrictEquals`, `isNot`. - `t.looseEqual(a, b, msg)` — assert `a == b`. Alias: `looseEquals`. - `t.notLooseEqual(a, b, msg)` — assert `a != b`. Alias: `notLooseEquals`. - `t.deepEqual(a, b, msg)` — recursive strict deep equality. Aliases: `same`, `deepEquals`, `isEquivalent`. - `t.notDeepEqual(a, b, msg)` — assert not deeply equal. Aliases: `notSame`, `notDeepEquals`, `notEquivalent`, `notDeeply`, `isNotDeepEqual`, `isNotEquivalent`. - `t.deepLooseEqual(a, b, msg)` — recursive loose deep equality. - `t.notDeepLooseEqual(a, b, msg)` — assert not deeply loosely equal. - `t.throws(fn, msg)` — assert fn() throws. - `t.doesNotThrow(fn, msg)` — assert fn() does not throw. - `t.matchString(string, regexp, msg)` — assert string matches regexp. - `t.doesNotMatchString(string, regexp, msg)` — assert string does not match regexp. - `t.match(a, b, msg)` — assert a matches pattern b (structural). - `t.doesNotMatch(a, b, msg)` — assert a does not match pattern b. - `t.rejects(promise, msg)` — assert promise rejects. **Async: must await.** Alias: `doesNotResolve`. - `t.resolves(promise, msg)` — assert promise resolves. **Async: must await.** Alias: `doesNotReject`. #### Embedded tests (all async, should be awaited) - `await t.test(name, options, testFn)` — nested test. Top-level `test()`/`it()` also works (auto-delegates). - `await t.skip(name, options, testFn)` — nested skipped test. - `await t.todo(name, options, testFn)` — nested TODO test. - `await t.asPromise(name, options, testPromiseFn)` — nested callback-style test. #### Utilities - `t.plan(n)` — record expected direct-assertion count; emits a `# plan != count: expected N, ran M` TAP comment at test end on mismatch (diagnostic, doesn't fail). - `t.comment(msg)` — emit a comment. - `t.skipTest(msg)` — skip current test with a message. - `t.bailOut(msg)` — abort entire test suite. ### Plugins - `registerTesterMethod(name, fn)` — install a method on `Tester.prototype` from a plugin module. Idempotent for same `fn`; throws on collision with a different `fn`. Plugin imports are per-file (workers / subprocesses / iframes don't share module graphs). ### Hooks Hooks can be registered as standalone functions or via test options or Tester methods. ```js import {test, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six'; // `before` is an alias for `beforeAll`, `after` is an alias for `afterAll`. beforeAll(() => { /* runs once before all tests */ }); afterAll(() => { /* runs once after all tests */ }); beforeEach(() => { /* runs before each test */ }); afterEach(() => { /* runs after each test */ }); // Inside a test — top-level functions auto-delegate to current tester: test('suite', async t => { beforeAll(() => { /* before embedded tests */ }); afterAll(() => { /* after embedded tests */ }); beforeEach(() => { /* before each embedded test */ }); afterEach(() => { /* after each embedded test */ }); // equivalent: t.beforeAll(), t.afterAll(), t.beforeEach(), t.afterEach() await t.test('inner', t => { t.pass(); }); }); // Or via test.beforeAll(), test.afterAll(), etc: test.beforeAll(() => { /* ... */ }); ``` ## Subpath modules ### tape-six/server — HTTP server harness Helpers for tests that need an ephemeral `node:http` server. Cross-runtime (Node, Bun, Deno; not for browser-side tests). ```js import {withServer, startServer, setupServer} from 'tape-six/server'; ``` - `withServer(serverHandler, clientHandler, opts?)` — scoped resource: spin up a server, run the test body with the base URL, tear down in `finally`. The 95% case. - `startServer(server, opts?)` — procedural primitive: returns `{server, base, port, host, close}`. For multi-phase tests or non-test code (e.g., `bin/tape6-server`) that wants long-term control. Races `'listening'` against `'error'` so port-busy rejects instead of hanging. - `setupServer(serverHandler, opts?)` — registers `beforeAll`/`afterAll` and returns a live-getter context for suite-shared servers. Don't destructure the returned object at module load — properties read live state on each access. - Options: `{host = '127.0.0.1', port = 0}`. Default host is explicit IPv4. `serverHandler` is the per-request callback (Node calls it once per incoming request). `clientHandler` is the per-scope test body (called once with the base URL). Either side may be the SUT. ### tape-six/response — HTTP response helpers Reading helpers that work uniformly with both `Response` (fetch results) and `http.IncomingMessage` (Node low-level requests). ```js import {asText, asJson, asBytes, header, headers} from 'tape-six/response'; ``` - `asText(res)` — body as UTF-8 string. - `asJson(res)` — body parsed as JSON. - `asBytes(res)` — body as `Uint8Array`. - `header(res, name)` — single header value, case-insensitive. Returns `null` if absent. - `headers(res)` — all headers as a plain object with lowercase keys. ## Common patterns ### Basic test file ```js import test from 'tape-six'; test('arithmetic', t => { t.equal(2 + 2, 4, 'addition'); t.ok(10 > 5, 'comparison'); }); test('async operation', async t => { const result = await fetchData(); t.ok(result, 'got result'); t.equal(result.status, 200, 'status is 200'); }); ``` ### Testing exceptions ```js test('errors', async t => { t.throws(() => { throw new Error('boom'); }, 'should throw'); t.doesNotThrow(() => 42, 'should not throw'); await t.rejects(Promise.reject(new Error('fail')), 'should reject'); await t.resolves(Promise.resolve(42), 'should resolve'); }); ``` ### Nested tests with setup/teardown ```js test('database tests', async t => { let db; t.beforeAll(async () => { db = await connect(); }); t.afterAll(async () => { await db.close(); }); await t.test('insert', async t => { const result = await db.insert({name: 'Alice'}); t.ok(result.id, 'got an id'); }); await t.test('query', async t => { const rows = await db.query('SELECT * FROM users'); t.ok(rows.length > 0, 'has rows'); }); }); ``` ### Using any for partial matching ```js test('partial match', t => { const result = {id: 123, name: 'Alice', timestamp: Date.now()}; t.deepEqual(result, {id: 123, name: 'Alice', timestamp: t.any}); }); ``` ### Using describe/it style ```js import {describe, it, before, beforeEach} from 'tape-six'; describe('my module', () => { before(() => { /* setup — auto-delegates to current tester */ }); beforeEach(() => { /* per-test setup */ }); it('should work', t => { t.ok(true); }); }); ``` ### Testing HTTP code with `withServer` ```js import test from 'tape-six'; import {withServer} from 'tape-six/server'; import {asJson} from 'tape-six/response'; test('GET /users returns the list', t => withServer(myHandler, async base => { const res = await fetch(`${base}/users`); t.equal(res.status, 200); const body = await asJson(res); t.equal(body.length, 3); })); ``` Suite-shared server (multiple tests against one instance): ```js import test, {beforeEach} from 'tape-six'; import {setupServer} from 'tape-six/server'; const server = setupServer((req, res) => { recorded.push({method: req.method, url: req.url}); res.writeHead(204).end(); }); let recorded; beforeEach(() => { recorded = []; }); test('records one request', async t => { await fetch(`${server.base}/foo`); t.equal(recorded.length, 1); }); ``` ### Skip and TODO ```js test.skip('not ready yet', t => { t.fail(); }); test.todo('work in progress', t => { t.equal(1, 2); }); // failure not counted ``` ## Running tests ```bash node test-file.js # run single file directly npx tape6 --flags FO # run all configured tests, show failures only + fail once npx tape6-seq # run sequentially (no worker threads) npx tape6-server --trace # start browser test server ``` ## Environment variables - `TAPE6_FLAGS` — flags string. - `TAPE6_PAR` — number of parallel workers. - `TAPE6_TAP` — force TAP reporter (any non-empty value). - `TAPE6_JSONL` — force JSONL reporter (any non-empty value). - `TAPE6_MIN` — force minimal reporter (any non-empty value). - `TAPE6_TEST_FILE_NAME` — set by runners to identify the current test file. ## Configuration (package.json) ```json { "scripts": { "test": "tape6 --flags FO" }, "tape6": { "tests": ["/tests/test-*.*js"] } } ``` ## Links - Docs: https://github.com/uhop/tape-six/wiki - npm: https://www.npmjs.com/package/tape-six - Full LLM reference: https://github.com/uhop/tape-six/blob/master/llms-full.txt