Property-Based Testing Changed How We Think About Correctness
Moving beyond example-based tests to specification-driven validation. A practical guide with fast-check.
For years, we wrote tests the same way everyone does: think of a few examples, assert the expected output. It works, but it leaves blind spots — the edge cases you didn't think to test.
The Shift
Property-based testing flips the script. Instead of testing specific inputs, you define properties — universal truths that should hold for all valid inputs. The framework then generates hundreds (or thousands) of random inputs to try to break your property.
A Practical Example
Consider a category filter. The property: "filtering by category X returns exactly the products with category X." We don't pick specific products — fast-check generates random product arrays and random filter values, checking this property holds every time.
This caught a bug in our filter logic where empty arrays were handled differently than arrays with a single item. No amount of hand-written examples would have caught that specific combination.
Our Setup
We use fast-check with Vitest. Each property test runs 100+ iterations minimum. We tag them with the design document property they validate:
- "Schema accepts valid products and rejects incomplete ones"
- "Category filtering returns exactly matching products"
- "Webhook processing idempotency"
When to Use It
Property-based tests shine for:
- Data transformations — serialization round-trips, encoding/decoding
- Business rules — "filtered results are always a subset of the original"
- Security boundaries — "invalid tokens always get rejected"
- Idempotency — "processing the same event twice has the same result as processing it once"
They're not a replacement for example-based tests — they're a complement. Use examples for documentation and specific scenarios. Use properties for confidence that your system is correct in the general case.