Property-based testing specifies behavior separately from individual test cases. Instead of collecting only examples, state an invariant—a relationship that should hold for arbitrary inputs—and let generators produce inputs that satisfy the stated constraints. A property remains valuable because it describes what an API must preserve, not merely what happened to work for selected values. section 8.0 section 8.6
A generator such as Gen[List[Int]] is not a list; it is a value that can create many lists. Prop.forAll combines a generator with an A => Boolean predicate. Running the resulting property searches generated cases for a falsification, reports a counterexample when one is found, and otherwise records that the tested cases passed. section 8.1 section 8.6
Listing 8.1 ScalaCheck properties
import org.scalacheck.{Gen, Prop}
val intList = Gen.listOf(Gen.choose(0, 100)) #1
val prop = #2
Prop.forAll(intList)(ns =>
ns.reverse.reverse == ns) && #3
Prop.forAll(intList)(ns =>
ns.headOption == ns.reverse.lastOption) #4
val failingProp =
Prop.forAll(intList)(ns => ns.reverse == ns) #5
This listing contrasts two useful claims about reverse: reversing twice restores a list, and a list’s first optional element matches the reversed list’s last. The deliberately false claim that every list equals its reverse shows why generated counterexamples matter. section 8.1
Properties form a partial specification. For List.reverse, double reversal and the headOption/lastOption relationship constrain behavior without describing an implementation. For List.sum, useful properties can relate a list to its reversal or characterize special inputs such as lists whose elements are all zero. section 8.1
Refining a maximum property
A first attempt says that a list’s maximum is at least every element. Generated empty lists expose a hidden precondition: List.max crashes on them. Rather than weakening the check after the fact, express the domain explicitly with a nonempty-list generator, then test that no generated element exceeds max. section 8.2.2 section 8.6
Generated testing does not replace ordinary examples. A fixed Boolean claim should use a direct verification primitive rather than repeatedly generating an irrelevant unit value. This distinction also sharpens results: a generated property may be unfalsified after some cases, whereas a finite domain tested completely can be proved. section 8.2.3 section 8.6
A practical property-testing design needs composable representations for both data and claims:
- Use Gen and sized SGen values to generate arbitrary inputs, including lists whose size varies with the test size. section 8.6 section 8.6
- Combine properties with && and ||, preserving useful failure context through tags. section 8.1 section 8.6
- Minimize failing inputs when possible so a counterexample is easier to investigate. section 8.6
- Use exhaustive generation only for finite, practically small domains; otherwise random sampling can establish only that no failure was found. section 8.6 section 8.6
The same approach checks algebraic laws. Parallel computations can be tested against laws such as mapping identity or mapping over unit, while generators vary both input values and executor services. If the generic expression obscures intent, introduce helpers such as forAllPar or verifyPar rather than accepting a technically expressive but unreadable test. section 8.2.3 section 8.0







