Property-Based Testing
States an invariant that should hold for any valid input, and generates many varied inputs (including edge cases) to check it, shrinking failures to a minimal reproducer.
Purpose
Property-based testing checks a general property across many generated inputs, rather than one specific example β complementing example-based tests by catching edge cases nobody thought to write an example for.
Example
import fc from 'fast-check';
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const reversedTwice = [...[...arr].reverse()].reverse();
return JSON.stringify(reversedTwice) === JSON.stringify(arr);
}),
);
Behavior notes
- A property-based testing library generates dozens or hundreds of varied inputs per run, including deliberately tricky edge cases (empty collections, boundary values, duplicates).
- On a failure, the library shrinks the failing input down to the smallest example that still reproduces it β far more useful for debugging than the (often large, complex) input that first triggered the failure.
- Good properties to look for: invariants (an operation applied twice returns to the start),
round-trips (
parse(serialize(x)) === x), and equivalence between two implementations of the same operation.
Common mistakes
- Writing a property so weak itβs trivially satisfied by an incorrect implementation (checking only that the output is an array, for instance, rather than something meaningful about its content).
- Using property-based tests to replace all example-based tests β specific examples still document intent more clearly for a human reader.
Runtime notes
A testing technique, not a language feature β implemented by libraries (fast-check for JavaScript, similar tools in most languages), applicable regardless of runtime.