tape-six-fast-check
v1.0.0
Published
Property-based testing plugin for tape-six powered by fast-check: t.prop() and t.scheduler() tester methods with structured counterexamples, seeds, and replay.
Maintainers
Readme
tape-six-fast-check 
tape-six-fast-check brings property-based testing to tape-six, powered by fast-check: two tester methods — t.prop() for properties and t.scheduler() for race-condition testing — reported as counted tape-six assertions with structured counterexamples, seeds, and replay paths.
ES modules, TypeScript bindings included. Works wherever tape-six and fast-check run: Node, Deno, Bun, and browsers (with an import map).
import test from 'tape-six';
import fc from 'fast-check';
import 'tape-six-fast-check';
test('sorting is idempotent', async t => {
await t.prop(
[fc.array(fc.integer())],
a => {
const once = [...a].sort((x, y) => x - y);
const twice = [...once].sort((x, y) => x - y);
return JSON.stringify(once) === JSON.stringify(twice);
},
'sort(sort(a)) === sort(a)'
);
});- On success: one counted, named passing assertion — visible in the plan and the TAP output like any other
t.*assertion. - On failure: one failing assertion carrying the shrunk counterexample, the seed, and the replay path as structured data (read from
fc.check()run details, never parsed from message text), located at thet.prop()call site.
Install
npm install --save-dev tape-six-fast-check tape-six fast-checktape-six (≥ 1.16.0) and fast-check (≥ 4) are peer dependencies — your project supplies them; the plugin pins neither.
Usage
Import the package once per test file — it registers the tester methods as a side effect:
import 'tape-six-fast-check';t.prop()
await t.prop(arbitraries, predicate, options?, msg?)Runs fc.check() over the given property and reports one aggregate assertion on the current test. Returns the full fast-check run details.
arbitraries— a non-empty array of fast-check arbitraries, one per predicate argument.predicate— the property body:(...values) => boolean | void | Promise<boolean | void>. Throw or returnfalseto fail; any other outcome passes. Sync or async.options—fc.check()parameters:numRuns,seed,path,endOnFailure, …msg— the assertion name (defaults to'property holds'). May take the options slot:t.prop(arbs, predicate, 'name').
Do not call t.* assertions inside the predicate — it runs up to numRuns times, so every inner assertion would be re-reported on each run. Signal through the return value or by throwing; the plugin reports a single aggregate assertion per t.prop() call.
test('addition is commutative', async t => {
await t.prop([fc.integer(), fc.integer()], (a, b) => a + b === b + a, 'a + b === b + a');
});Replaying a failure
A failed run reports its counterexample, seed, and path as structured assertion data. Pin them in options to replay the exact shrunk case:
await t.prop([fc.array(fc.integer())], mySortIsStable, {
seed: -1651341797,
path: '25:3:1',
endOnFailure: true
});t.scheduler()
await t.scheduler(body, options?, msg?)Race-condition testing sugar over fc.asyncProperty(fc.scheduler(), body). The body receives fast-check's cooperative scheduler: wrap promises with s.schedule(...) (or s.scheduleFunction(...)), drain with await s.waitAll(), and fast-check explores task interleavings across runs. Throw or return false to fail; the same aggregate reporting and replay mechanics as t.prop() apply.
optionsadditionally acceptsact— passed through tofc.scheduler({act})(for React-style wrappers).
test('last write wins regardless of interleaving', async t => {
await t.scheduler(async s => {
let state = 'initial';
s.schedule(Promise.resolve()).then(() => (state = 'a'));
s.schedule(Promise.resolve()).then(() => (state = 'b'));
await s.waitAll();
return state !== 'initial';
}, 'some write always lands');
});TypeScript
The typings augment tape-six's Tester interface, so t.prop / t.scheduler are fully typed after a single import 'tape-six-fast-check'. Predicate argument types are inferred from the arbitraries tuple:
await t.prop([fc.integer(), fc.string()], (n, s) => typeof n === 'number' && s.length >= 0);Exported helper types: ArbitraryTuple, PropPredicate, SchedulerBody, SchedulerOptions.
In the browser
tape-six runs browser tests off plain ES modules and an import map served by tape6-server — the setup is documented in the tape-six wiki: Set-up tests and Environment ‐ Browsers. To use t.prop() / t.scheduler() there, extend that import map with this package, fast-check, and the pure-rand subpaths fast-check imports:
<script type="importmap">
{
"imports": {
"tape-six": "/node_modules/tape-six/index.js",
"tape-six/": "/node_modules/tape-six/src/",
"tape-six-fast-check": "/node_modules/tape-six-fast-check/index.js",
"fast-check": "/node_modules/fast-check/lib/fast-check.js",
"pure-rand/distribution/uniformBigInt": "/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.js",
"pure-rand/distribution/uniformInt": "/node_modules/pure-rand/lib/esm/distribution/uniformInt.js",
"pure-rand/generator/congruential32": "/node_modules/pure-rand/lib/esm/generator/congruential32.js",
"pure-rand/generator/mersenne": "/node_modules/pure-rand/lib/esm/generator/mersenne.js",
"pure-rand/generator/xoroshiro128plus": "/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.js",
"pure-rand/generator/xorshift128plus": "/node_modules/pure-rand/lib/esm/generator/xorshift128plus.js",
"pure-rand/utils/skipN": "/node_modules/pure-rand/lib/esm/utils/skipN.js"
}
}
</script>The pure-rand entries are needed because import maps don't apply a package's exports table: fast-check imports extensionless subpaths (pure-rand/utils/skipN), so each one is mapped to its .js file explicitly. The list mirrors fast-check 4.9.0 — re-check it after a major fast-check bump. Alternatively, a single CDN entry replaces the fast-check and pure-rand lines — the CDN resolves the dependency itself:
"fast-check": "https://cdn.jsdelivr.net/npm/fast-check@4/+esm"Release notes
- 1.0.0 — Initial release:
t.prop(),t.scheduler(), TypeScript augmentation ofTester.
License
BSD-3-Clause © 2026 Eugene Lazutkin.
