Prop represents a testable claim without immediately printing a report or otherwise discarding its outcome. A property is run later with explicit test configuration and random state, producing a Result: Passed, Falsified with a failing case and success count, or Proved. Keeping that result as data makes properties reusable and composable. section 8.1.3 section 8.1.6
A Boolean-returning check is enough to sketch conjunction, but not enough to explain a failure. Prop therefore carries failure information rather than treating execution as the representation. Its refined form is a function from maximum size, number of test cases, and RNG to Result; each dependency required for generating and evaluating cases is explicit. section 8.1.3 section 8.2
This representation supports several distinct kinds of property:
- Generated properties: forAll turns a Gen or SGen plus a predicate into a Prop, stopping when a generated value falsifies the predicate or throws an exception. section 8.1.6
- Combined properties: && runs the right property only after the left passes, while || tries the right property after a left failure; tags can preserve the failure path. section 8.6
- Directly verified properties: verify evaluates a hardcoded Boolean once and can report Proved rather than pretending it survived many random tests. section 8.2.3
- Sized properties: Prop distributes testing across sizes up to a maximum, allowing SGen-based tests to seek smaller failing cases. section 8.2
Specifying List.max
Generate lists of small integers and assert that their maximum is at least every element. The first version exposes an unstated precondition: max fails on an empty list. Switching to a nonempty-list generator makes the property state the intended domain as well as the expected result. section 8.2.2 section 8.6
Testing laws for parallel computations
Prop can express both a fixed equality, such as mapping +1 over Par.unit(1), and a generated law over arbitrary integers. Helper generators for ExecutorService and Par values move setup details behind forAllPar, leaving the law visible. section 8.2.3
Listing 8.3 Implementing forAll
def forAll[A](as: Gen[A])(f: A => Boolean): Prop =
(n, rng) =>
randomLazyList(as)(rng)
.zip(LazyList.from(0))
.take(n)
.map: #1
case (a, i) =>
try
if f(a) then Passed
else Falsified(a.toString, i) #2
catch
case e: Exception =>
Falsified(buildMsg(a, e), i) #3
.find(_.isFalsified)
.getOrElse(Passed)
}
def randomLazyList[A](g: Gen[A])(rng: RNG): LazyList[A] =
LazyList.unfold(rng)(rng => Some(g.run(rng))) #4
def buildMsg[A](s: A, e: Exception): String =
s"test case: $s\n" + #5
s"generated an exception: ${e.getMessage}\n" +
s"stack trace:\n ${e.getStackTrace.mkString("\n")}"
The forAll implementation shows the operational core: generate a lazy stream from an RNG, test at most n cases, and return the first falsification or Passed. section 8.1.6
