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.

Taught in these lessons

Updated 2026-09-03