0 day streak
typescript / expert
Snippet

Property-Based Testing with Type-Safe Generators

Example-based tests check single inputs; property-based tests assert invariants over a generated sample space. Modelling a generator as `Gen<T> = () => T` makes them composable through plain functions — `array(int(...))` is a generator of arrays of ints — and TypeScript carries `T` through the combinator chain so the property receives the correct type. The `check` driver runs hundreds of pseudo-random samples and throws on the first counter-example, surfacing edge cases hand-written tests rarely cover.

snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Gen<T> = () => T;
const int = (min: number, max: number): Gen<number> =>
() => Math.floor(Math.random() * (max - min + 1)) + min;
const array = <T>(g: Gen<T>, max = 20): Gen<T[]> =>
() => Array.from({ length: int(0, max)() }, g);
function check<T>(gen: Gen<T>, prop: (x: T) => boolean, runs = 100): void {
for (let i = 0; i < runs; i++) {
const sample = gen();
if (!prop(sample)) throw new Error(`failed for ${JSON.stringify(sample)}`);
}
}
check(array(int(-50, 50)), (xs) => xs.slice().reverse().reverse().length === xs.length);
Breakdown
1
type Gen<T> = () => T;
Generators are nullary thunks returning T — easy to compose.
2
const array = <T>(g: Gen<T>, max = 20): Gen<T[]> =>
Combinator: lifts a value generator into a generator of arrays.
3
function check<T>(gen: Gen<T>, prop: (x: T) => boolean, runs = 100): void {
Runs the property `runs` times against fresh samples.
4
check(array(int(-50, 50)), (xs) => xs.slice().reverse().reverse().length === xs.length);
Double-reverse preserves length — an invariant verified over many random arrays.