Explore property-based testing with ScalaCheck as a way to test invariants, generators, and shrinking behavior rather than relying only on a handful of fixed examples.
Property-based testing: A testing approach in which you specify invariant properties that should hold across many generated inputs rather than writing only a few hand-picked examples.
Scala is especially friendly to property-based testing because immutable data, ADTs, and pure functions make invariants easier to state and easier to check repeatedly.
Example-based tests answer:
Property-based tests answer:
That makes them useful for:
1import org.scalacheck.Prop.forAll
2
3def normalizeEmail(raw: String): String =
4 raw.trim.toLowerCase
5
6val normalizationIsIdempotent =
7 forAll { raw: String =>
8 normalizeEmail(normalizeEmail(raw)) == normalizeEmail(raw)
9 }
This property is valuable because it checks something structural: normalization should not keep changing the value after the first pass.
Weak generators lead to weak confidence. If your generated values rarely hit edge cases, the property may pass without teaching you much.
Good generators should deliberately cover:
The real skill is not just writing forAll, but shaping generated data so the test explores meaningful risk.
One of ScalaCheck’s biggest benefits is shrinking: when a property fails, the framework tries to reduce the failing case to a smaller example. That turns a messy random failure into something a human can reason about.
Treat the shrunk counterexample as a design clue. It often exposes an invariant you forgot to model explicitly.
Property-based testing is not a total replacement for example-based tests.
Keep example tests for:
Use properties for broader invariants and input coverage. The two styles work better together than either does alone.
The property restates the implementation or checks something too weak to catch meaningful bugs.
The generator sprays data widely, but not in ways that stress the actual risk boundaries.
Property-based testing increases search breadth, but it still depends on good invariants and good generators.
Use ScalaCheck where invariants matter more than a few named examples. Spend real effort on generators, keep properties meaningful and reviewable, and preserve example tests for the domain scenarios that humans actually talk about.