How property-based testing finds bugs you never thought to test
property-based testing, invariants, fast-check, fuzz testing, generating edge cases automatically
Beyond Example-Based Tests
Example-based tests check specific inputs you thought of. Property-based tests check invariants -- properties that must hold true for any valid input -- and let the framework generate hundreds of random test cases automatically, including edge cases you would never manually write.
Defining Properties
An invariant is a statement that must always be true: sorting an array should not change its length, encoding then decoding should return the original string, discount should never make price negative. If any generated input violates the invariant, the framework reports the minimal failing case.
// fast-check (JavaScript)
const fc = require('fast-check');
// Property: sorted array always has same length as input
fc.assert(
fc.property(fc.array(fc.integer()), arr => {
const sorted = [...arr].sort((a, b) => a - b);
return sorted.length === arr.length;
})
);
// Property: discount never produces negative price
fc.assert(
fc.property(
fc.float({ min: 0, max: 1000 }),
fc.float({ min: 0, max: 100 }),
(price, discount) => {
const result = applyDiscount(price, discount);
return result >= 0;
}
)
); // Will find the case where discount > price
Property-based testing is most powerful for pure functions. Use it when you want confidence that a core algorithm is correct for a wide range of inputs, not just the cases you remembered to test.
