From nw
Guides property-based testing in TypeScript/JavaScript using fast-check, with arbitraries for primitives/collections/combinators, recursive generators, and stateful model-based testing.
npx claudepluginhub nwave-ai/nwave --plugin nwThis skill uses the workspace's default tool permissions.
**fast-check** is the dominant PBT framework for TypeScript/JavaScript. No serious competitors.
Generates property-based tests using fast-check (TypeScript/JavaScript with Vitest) and Hypothesis (Python) to find edge cases, verify properties like roundtrips and invariants.
Provides property-based testing fundamentals: core concepts, property taxonomy with decision tables/trees, and selection strategies. Language-agnostic.
Guides property-based testing for serialization, validation, normalization, and pure functions with property catalog, pattern detection, and library references.
Share bugs, ideas, or general feedback.
fast-check is the dominant PBT framework for TypeScript/JavaScript. No serious competitors.
import fc from 'fast-check';
test('sort is idempotent', () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = [...arr].sort((a, b) => a - b);
const twice = [...sorted].sort((a, b) => a - b);
expect(sorted).toEqual(twice);
}),
{ numRuns: 1000 }
);
});
fc.integer() // any safe integer
fc.integer({ min: 0, max: 99 })
fc.nat() // non-negative integer
fc.float()
fc.double()
fc.string()
fc.string({ minLength: 1, maxLength: 50 })
fc.boolean()
fc.constant(null)
fc.constantFrom(1, 2, 3) // pick from values
fc.array(fc.integer())
fc.array(fc.integer(), { minLength: 1, maxLength: 10 })
fc.set(fc.integer())
fc.dictionary(fc.string(), fc.integer())
fc.tuple(fc.integer(), fc.string())
fc.oneof(fc.integer(), fc.string()) // union
fc.option(fc.integer()) // T | null
// Map
fc.integer().map(n => n * 2) // even integers
// Filter
fc.integer().filter(n => n > 0)
// Prefer: fc.integer({ min: 1 })
// Chain (dependent generation)
fc.array(fc.integer(), { minLength: 1 }).chain(
arr => fc.tuple(fc.constant(arr), fc.integer({ min: 0, max: arr.length - 1 }))
)
// Record
fc.record({
name: fc.string({ minLength: 1 }),
age: fc.integer({ min: 0, max: 150 }),
active: fc.boolean(),
})
// Frequency (weighted)
fc.frequency(
{ weight: 80, arbitrary: fc.char() },
{ weight: 10, arbitrary: fc.constant(' ') },
{ weight: 1, arbitrary: fc.constantFrom('.', '-') }
)
const jsonArb = fc.letrec(tie => ({
value: fc.oneof(
fc.constant(null), fc.boolean(), fc.integer(), fc.string(),
fc.array(tie('value')), fc.dictionary(fc.string(), tie('value'))
),
})).value;
import fc from 'fast-check';
type Model = { items: Map<string, number> };
class PutCommand implements fc.Command<Model, MyStore> {
constructor(readonly key: string, readonly value: number) {}
check = (m: Readonly<Model>) => true;
run(m: Model, r: MyStore): void {
r.put(this.key, this.value);
m.items.set(this.key, this.value);
}
toString = () => `put(${this.key}, ${this.value})`;
}
class GetCommand implements fc.Command<Model, MyStore> {
constructor(readonly key: string) {}
check(m: Readonly<Model>): boolean {
return m.items.has(this.key); // precondition
}
run(m: Model, r: MyStore): void {
expect(r.get(this.key)).toBe(m.items.get(this.key));
}
toString = () => `get(${this.key})`;
}
const allCommands = [
fc.tuple(fc.string(), fc.integer()).map(([k, v]) => new PutCommand(k, v)),
fc.string().map(k => new GetCommand(k)),
];
test('store matches model', () => {
fc.assert(
fc.property(fc.commands(allCommands), (cmds) => {
const setup = () => ({
model: { items: new Map() },
real: new MyStore(),
});
fc.modelRun(setup, cmds);
})
);
});
fc.assert(
fc.property(fc.scheduler(), fc.commands(allCommands), async (s, cmds) => {
const setup = () => ({
model: { items: new Map() },
real: new MyStore(s), // system must use scheduler for async ops
});
await fc.scheduledModelRun(setup, cmds);
})
);
Scheduler controls promise resolution order, enabling deterministic exploration of async interleavings.
// Jest -- works out of the box
// Vitest -- works out of the box
// With @fast-check/jest (enhanced integration)
import { test } from '@fast-check/jest';
test.prop([fc.integer(), fc.integer()])('commutative addition', (a, b) => {
expect(a + b).toBe(b + a);
});
// With @fast-check/vitest
import { test } from '@fast-check/vitest';
test.prop([fc.string()])('string length non-negative', (s) => {
expect(s.length).toBeGreaterThanOrEqual(0);
});
// Replay failing tests
fc.assert(
fc.property(fc.integer(), (n) => { /* ... */ }),
{ seed: 1234567890, path: '4:1:0' } // from failure output
);
fc.scheduler() controls async interleaving -- unique outside Erlang{ size: '+1' } controls generation complexity growth