A comprehensive property-based testing library for Scala and Java applications that enables developers to specify program properties as testable assertions and automatically generates test cases to verify these properties.
—
The ScalaCheck Arbitrary framework provides type-directed generator instances for automatic property parameterization. It eliminates the need to explicitly specify generators for common types and enables seamless integration with the property testing system.
The fundamental type class that wraps generators and provides implicit resolution.
sealed abstract class Arbitrary[T] {
def arbitrary: Gen[T]
}
object Arbitrary {
def apply[T](g: => Gen[T]): Arbitrary[T]
def arbitrary[T](implicit a: Arbitrary[T]): Gen[T]
}Usage Examples:
// Create custom Arbitrary instance
case class Person(name: String, age: Int)
implicit val arbPerson: Arbitrary[Person] = Arbitrary {
for {
name <- Gen.alphaStr.suchThat(_.nonEmpty)
age <- Gen.choose(0, 120)
} yield Person(name, age)
}
// Use implicit generator
val personProperty = forAll { (p: Person) =>
p.age >= 0 && p.name.nonEmpty
}
// Get generator explicitly
val personGen: Gen[Person] = Arbitrary.arbitrary[Person]Implicit generators for basic Scala and Java primitive types.
implicit val arbBool: Arbitrary[Boolean]
implicit val arbInt: Arbitrary[Int]
implicit val arbLong: Arbitrary[Long]
implicit val arbFloat: Arbitrary[Float]
implicit val arbDouble: Arbitrary[Double]
implicit val arbChar: Arbitrary[Char]
implicit val arbByte: Arbitrary[Byte]
implicit val arbShort: Arbitrary[Short]
implicit val arbUnit: Arbitrary[Unit]
implicit val arbAnyVal: Arbitrary[AnyVal]Usage Examples:
// These work automatically due to implicit instances
val intProp = forAll { (x: Int) => x + 0 == x }
val boolProp = forAll { (b: Boolean) => b || !b }
val charProp = forAll { (c: Char) => c.toString.length == 1 }
val floatProp = forAll { (f: Float) => !f.isNaN ==> (f + 0.0f == f) }Generators for various string and symbolic text representations.
implicit val arbString: Arbitrary[String]
implicit val arbSymbol: Arbitrary[Symbol]Usage Examples:
val stringProp = forAll { (s: String) =>
s.reverse.reverse == s
}
val symbolProp = forAll { (sym: Symbol) =>
Symbol(sym.name) == sym
}Arbitrary instances for big numeric types and Java numeric wrappers.
implicit val arbBigInt: Arbitrary[BigInt]
implicit val arbBigDecimal: Arbitrary[BigDecimal]
implicit val arbNumber: Arbitrary[Number]Usage Examples:
val bigIntProp = forAll { (x: BigInt, y: BigInt) =>
x + y == y + x
}
val bigDecimalProp = forAll { (x: BigDecimal) =>
x * BigDecimal(1) == x
}Generators for temporal types including legacy Java date/time and Scala durations.
implicit val arbDate: Arbitrary[java.util.Date]
implicit val arbCalendar: Arbitrary[java.util.Calendar]
implicit val arbFiniteDuration: Arbitrary[FiniteDuration]
implicit val arbDuration: Arbitrary[Duration]Usage Examples:
val dateProp = forAll { (d: java.util.Date) =>
new java.util.Date(d.getTime) == d
}
val durationProp = forAll { (d1: FiniteDuration, d2: FiniteDuration) =>
(d1 + d2) >= d1 && (d1 + d2) >= d2
}Generators for exception and error hierarchies.
implicit val arbThrowable: Arbitrary[Throwable]
implicit val arbException: Arbitrary[Exception]
implicit val arbError: Arbitrary[Error]Usage Examples:
val exceptionProp = forAll { (e: Exception) =>
e.getMessage != null || e.getMessage == null // Either is valid
}Generators for UUIDs, bit sets, and other utility types.
implicit val arbUuid: Arbitrary[java.util.UUID]
implicit val arbBitSet: Arbitrary[collection.BitSet]Usage Examples:
val uuidProp = forAll { (u: java.util.UUID) =>
java.util.UUID.fromString(u.toString) == u
}
val bitSetProp = forAll { (bs: collection.BitSet) =>
bs union bs == bs
}Automatic generator derivation for generic types and type constructors.
implicit def arbOption[T](implicit a: Arbitrary[T]): Arbitrary[Option[T]]
implicit def arbEither[T, U](implicit at: Arbitrary[T], au: Arbitrary[U]): Arbitrary[Either[T, U]]
implicit def arbTry[T](implicit a: Arbitrary[T]): Arbitrary[Try[T]]
implicit def arbFuture[T](implicit a: Arbitrary[T]): Arbitrary[Future[T]]Usage Examples:
val optionProp = forAll { (opt: Option[String]) =>
opt.map(_.length).getOrElse(0) >= 0
}
val eitherProp = forAll { (e: Either[String, Int]) =>
e.isLeft || e.isRight
}
val tryProp = forAll { (t: Try[Int]) =>
t.isSuccess || t.isFailure
}Automatic generator derivation for various collection types.
implicit def arbContainer[C[_], T](
implicit a: Arbitrary[T],
b: Buildable[T, C[T]]
): Arbitrary[C[T]]
implicit def arbContainer2[C[_, _], T, U](
implicit at: Arbitrary[T],
au: Arbitrary[U],
b: Buildable[(T, U), C[T, U]]
): Arbitrary[C[T, U]]Usage Examples:
val listProp = forAll { (l: List[Int]) =>
l.reverse.reverse == l
}
val vectorProp = forAll { (v: Vector[String]) =>
v.length >= 0
}
val mapProp = forAll { (m: Map[String, Int]) =>
m.keys.size <= m.size
}
val setProp = forAll { (s: Set[Double]) =>
s union s == s
}Generators for function types using co-generators.
implicit def arbFunction0[T](implicit a: Arbitrary[T]): Arbitrary[() => T]
implicit def arbPartialFunction[A, B](
implicit aa: Arbitrary[A],
ca: Cogen[A],
ab: Arbitrary[B]
): Arbitrary[PartialFunction[A, B]]Usage Examples:
val function0Prop = forAll { (f: () => Int) =>
f() == f() // Should be deterministic
}
val pfProp = forAll { (pf: PartialFunction[Int, String], x: Int) =>
pf.isDefinedAt(x) ==> (pf(x) != null)
}Automatic generator derivation for Java enumeration types.
implicit def arbEnum[A <: java.lang.Enum[A]](
implicit tag: reflect.ClassTag[A]
): Arbitrary[A]Usage Examples:
// For Java enum types
enum Color { RED, GREEN, BLUE }
val colorProp = forAll { (c: Color) =>
c == Color.RED || c == Color.GREEN || c == Color.BLUE
}Generators for ScalaCheck's own types, useful for meta-testing.
implicit val arbProp: Arbitrary[Prop]
implicit val arbTestParameters: Arbitrary[Test.Parameters]
implicit val arbGenParams: Arbitrary[Gen.Parameters]
implicit def arbGen[T](implicit a: Arbitrary[T]): Arbitrary[Gen[T]]Usage Examples:
val propProp = forAll { (p: Prop) =>
// Meta-property: properties should be checkable
try { p.check(); true } catch { case _: Exception => true }
}
val genProp = forAll { (g: Gen[Int]) =>
g.sample.isDefined || g.sample.isEmpty
}case class Email(value: String)
implicit val arbEmail: Arbitrary[Email] = Arbitrary {
for {
user <- Gen.alphaNumStr.suchThat(_.nonEmpty)
domain <- Gen.alphaNumStr.suchThat(_.nonEmpty)
tld <- Gen.oneOf("com", "org", "net", "edu")
} yield Email(s"$user@$domain.$tld")
}sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
implicit def arbTree[A](implicit a: Arbitrary[A]): Arbitrary[Tree[A]] = Arbitrary {
val genLeaf = a.arbitrary.map(Leaf(_))
def genBranch(size: Int): Gen[Tree[A]] =
if (size <= 0) genLeaf
else Gen.oneOf(
genLeaf,
for {
left <- genBranch(size / 2)
right <- genBranch(size / 2)
} yield Branch(left, right)
)
Gen.sized(genBranch)
}case class PositiveInt(value: Int)
implicit val arbPositiveInt: Arbitrary[PositiveInt] = Arbitrary {
Gen.choose(1, Int.MaxValue).map(PositiveInt)
}
case class NonEmptyList[A](values: List[A])
implicit def arbNonEmptyList[A](implicit a: Arbitrary[A]): Arbitrary[NonEmptyList[A]] = Arbitrary {
Gen.nonEmptyListOf(a.arbitrary).map(NonEmptyList(_))
}case class User(
id: Long,
username: String,
email: String,
age: Int,
roles: Set[String],
settings: Map[String, String]
)
implicit val arbUser: Arbitrary[User] = Arbitrary {
for {
id <- Gen.posNum[Long]
username <- Gen.alphaNumStr.suchThat(s => s.length >= 3 && s.length <= 20)
email <- Arbitrary.arbitrary[Email].map(_.value)
age <- Gen.choose(13, 120)
roleCount <- Gen.choose(1, 5)
roles <- Gen.listOfN(roleCount, Gen.oneOf("user", "admin", "moderator", "guest"))
.map(_.toSet)
settingCount <- Gen.choose(0, 10)
settingKeys <- Gen.listOfN(settingCount, Gen.alphaStr.suchThat(_.nonEmpty))
settingValues <- Gen.listOfN(settingCount, Gen.alphaNumStr)
settings = settingKeys.zip(settingValues).toMap
} yield User(id, username, email, age, roles, settings)
}// Automatic derivation works through implicit chains
val nestedProp = forAll { (data: Map[String, List[Option[Either[Int, String]]]]) =>
// This works because:
// 1. Arbitrary[Int] and Arbitrary[String] exist
// 2. Arbitrary[Either[Int, String]] is derived
// 3. Arbitrary[Option[Either[Int, String]]] is derived
// 4. Arbitrary[List[Option[Either[Int, String]]]] is derived
// 5. Arbitrary[Map[String, List[Option[Either[Int, String]]]]] is derived
data.size >= 0
}// Sometimes you want a specific generator for a property
val customProp = forAll(Gen.choose(1, 10)) { (smallInt: Int) =>
// Uses explicit generator instead of Arbitrary[Int]
smallInt >= 1 && smallInt <= 10
}
// Mix explicit and implicit generators
val mixedProp = forAll(Gen.choose(1, 100), Arbitrary.arbitrary[String]) { (smallInt, str) =>
smallInt.toString.length <= str.length || str.isEmpty
}// Provide different generators based on type constraints
implicit def arbOrderedPair[T](implicit a: Arbitrary[T], ord: Ordering[T]): Arbitrary[(T, T)] = Arbitrary {
for {
x <- a.arbitrary
y <- a.arbitrary
} yield if (ord.lt(x, y)) (x, y) else (y, x)
}ScalaCheck provides built-in Arbitrary instances for Java 8 time types through the org.scalacheck.time package.
// Import for Java Time support
import org.scalacheck.time._
// Date and Time Types
implicit val arbDuration: Arbitrary[java.time.Duration]
implicit val arbInstant: Arbitrary[java.time.Instant]
implicit val arbLocalDate: Arbitrary[java.time.LocalDate]
implicit val arbLocalTime: Arbitrary[java.time.LocalTime]
implicit val arbLocalDateTime: Arbitrary[java.time.LocalDateTime]
implicit val arbOffsetTime: Arbitrary[java.time.OffsetTime]
implicit val arbOffsetDateTime: Arbitrary[java.time.OffsetDateTime]
implicit val arbZonedDateTime: Arbitrary[java.time.ZonedDateTime]
// Period and Year Types
implicit val arbPeriod: Arbitrary[java.time.Period]
implicit val arbYear: Arbitrary[java.time.Year]
implicit val arbYearMonth: Arbitrary[java.time.YearMonth]
implicit val arbMonthDay: Arbitrary[java.time.MonthDay]
// Zone Types
implicit val arbZoneOffset: Arbitrary[java.time.ZoneOffset]
implicit val arbZoneId: Arbitrary[java.time.ZoneId]Usage Examples:
import org.scalacheck.time._
import java.time._
val timestampProp = forAll { (instant: Instant) =>
instant.getEpochSecond >= Instant.MIN.getEpochSecond &&
instant.getEpochSecond <= Instant.MAX.getEpochSecond
}
val dateProp = forAll { (date: LocalDate) =>
date.isAfter(LocalDate.MIN) || date.isEqual(LocalDate.MIN)
}
val durationProp = forAll { (d1: Duration, d2: Duration) =>
d1.plus(d2).minus(d1) == d2
}
val zonedTimeProp = forAll { (zdt: ZonedDateTime) =>
zdt.toInstant.atZone(zdt.getZone) == zdt
}Install with Tessl CLI
npx tessl i tessl/maven-org-scalacheck--scalacheck-2-12