← Stackzilla Blog
Property-Based Testing: Finding Inputs You Would Not Think to Write
Published November 10, 2026
· 3 min read
· testing, TypeScript, quality, algorithms
Use generated inputs and meaningful invariants to discover edge cases beyond the examples in a conventional unit test.
Example-based tests are useful because they make expected behavior concrete. A few carefully chosen inputs can explain a function better than a page of prose. The problem is that developers tend to choose inputs they already understand.
Property-based testing starts with a rule that should hold across a range of inputs. A testing library generates cases, looks for a counterexample, and often shrinks a failing case into something easier to understand.
## Write a property that means something
For a sorting function, the output should be ordered and contain the same elements as the input. Checking only the order is insufficient: a broken function returning an empty array would pass.
A serialization round trip might preserve supported values, but define that supported set carefully. JSON does not preserve every JavaScript value, so a universal round-trip claim would be wrong.
Domain properties can be even more useful. Applying a discount should not make a valid price negative. Reordering independent items should not change a total. An authorization rule should never grant a user access to another tenant's resources.
## Generate valid data deliberately
Generators describe the input space. If they produce mostly impossible business objects, the test may spend its time rejecting inputs instead of exercising behavior.
Build data from domain constraints: currency amounts with supported precision, valid date ranges, or related records whose identifiers agree. Then add targeted generators for invalid inputs when testing validation.
Pay attention to boundaries that ordinary random generation might rarely hit. Empty lists, repeated elements, Unicode text, maximum lengths, and values around numeric limits often deserve explicit coverage.
## Make failures reproducible
Record the random seed and replay information reported by the library. A failing generated case should be reproducible locally and in CI.
Shrinking helps isolate the smallest counterexample under the generator's rules. Inspect it before changing the implementation: the failure may reveal an incorrect property rather than a product defect.
Keep a conventional regression test for an important discovered bug when that makes the lesson clearer. Generated tests and hand-written examples reinforce each other.
Property-based testing is particularly effective for parsers, transformations, validation, accounting rules, and state machines with many combinations. It is less useful when the property merely restates the implementation or the generator never reaches interesting cases.
Start with one function whose invariants are clear. The objective is not to replace every unit test with randomness. It is to systematically explore the parts of the input space that human-selected examples leave untouched.
## Further reading
- [fast-check: Getting started](https://fast-check.dev/docs/introduction/getting-started/)
- [Hypothesis: Quickstart](https://hypothesis.readthedocs.io/en/latest/quickstart.html)
Read the full article on Stackzilla →