Generator system for creating random test data and property-based testing with automatic shrinking support. Property-based testing validates code against general properties rather than specific examples.
The fundamental generator type for producing test data.
/**
* A generator of values of type A requiring environment R
* @tparam R - Environment type required for generation
* @tparam A - Type of values to generate
*/
case class Gen[-R, +A](sample: ZStream[R, Nothing, Sample[R, A]]) {
/** Transform generated values */
def map[B](f: A => B)(implicit trace: Trace): Gen[R, B]
/** Effectfully transform generated values */
def mapZIO[R1 <: R, B](f: A => ZIO[R1, Nothing, B])(implicit trace: Trace): Gen[R1, B]
/** Monadic composition of generators */
def flatMap[R1 <: R, B](f: A => Gen[R1, B])(implicit trace: Trace): Gen[R1, B]
/** Filter generated values by predicate */
def filter(f: A => Boolean)(implicit trace: Trace): Gen[R, A]
/** Effectfully filter generated values */
def filterZIO[R1 <: R](f: A => ZIO[R1, Nothing, Boolean])(implicit trace: Trace): Gen[R1, A]
/** Collect values using partial function */
def collect[B](pf: PartialFunction[A, B])(implicit trace: Trace): Gen[R, B]
/** Combine two generators */
def zip[R1 <: R, B](that: Gen[R1, B])(implicit trace: Trace): Gen[R1, (A, B)]
/** Combine generators with custom function */
def zipWith[R1 <: R, B, C](that: Gen[R1, B])(f: (A, B) => C)(implicit trace: Trace): Gen[R1, C]
/** Concatenate with another generator */
def concat[R1 <: R, A1 >: A](that: Gen[R1, A1])(implicit trace: Trace): Gen[R1, A1]
/** Disable shrinking for this generator */
def noShrink(implicit trace: Trace): Gen[R, A]
/** Replace shrinking strategy */
def reshrink[R1 <: R](f: A => ZStream[R1, Nothing, A])(implicit trace: Trace): Gen[R1, A]
/** Set size parameter for generator */
def resize(size: Int)(implicit trace: Trace): Gen[R, A]
}Container for generated values with shrinking information.
/**
* A sample contains a generated value and shrinking information
* @tparam R - Environment type
* @tparam A - Value type
*/
case class Sample[-R, +A](value: A, shrink: ZStream[R, Nothing, Sample[R, A]]) {
/** Transform the sample value */
def map[B](f: A => B): Sample[R, B]
/** Apply function to each element in shrink tree */
def foreach[R1 <: R, B](f: A => ZIO[R1, Nothing, B])(implicit trace: Trace): ZIO[R1, Nothing, Sample[R1, B]]
/** Search through shrinks for values matching predicate */
def shrinkSearch(f: A => Boolean)(implicit trace: Trace): ZStream[R, Nothing, A]
}Core functions for running property-based tests.
/**
* Check property with sufficient number of samples
* @param rv - Generator producing test values
* @param test - Property test function
* @return ZIO effect producing TestResult
*/
def check[R, A, In](rv: Gen[R, A])(test: A => In)(implicit
checkConstructor: CheckConstructor[R, In],
sourceLocation: SourceLocation,
trace: Trace
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
/**
* Check property for all values from finite generator
* @param rv - Finite generator producing test values
* @param test - Property test function
* @return ZIO effect producing TestResult
*/
def checkAll[R, A, In](rv: Gen[R, A])(test: A => In)(implicit
checkConstructor: CheckConstructor[R, In],
sourceLocation: SourceLocation,
trace: Trace
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
/**
* Check property with specific number of samples
* @param n - Number of samples to test
* @return CheckN instance for applying to generators
*/
def checkN(n: Int): CheckVariants.CheckN
/**
* Check property in parallel with sufficient samples
* @param rv - Generator producing test values
* @param parallelism - Number of parallel workers
* @param test - Property test function
* @return ZIO effect producing TestResult
*/
def checkPar[R, A, In](rv: Gen[R, A], parallelism: Int)(test: A => In)(implicit
checkConstructor: CheckConstructor[R, In],
sourceLocation: SourceLocation,
trace: Trace
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
/**
* Check all values from finite generator in parallel
* @param rv - Finite generator producing test values
* @param parallelism - Number of parallel workers
* @param test - Property test function
* @return ZIO effect producing TestResult
*/
def checkAllPar[R, A, In](rv: Gen[R, A], parallelism: Int)(test: A => In)(implicit
checkConstructor: CheckConstructor[R, In],
sourceLocation: SourceLocation,
trace: Trace
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]Usage Examples:
import zio.test._
import zio.test.Gen._
test("property-based testing") {
// Simple property test
check(int) { n =>
assertTrue((n + 1) > n)
} &&
// Test with multiple generators
check(int, string) { (n, s) =>
assertTrue(s.length >= 0 && n.toString.nonEmpty)
} &&
// Test with specific number of samples
checkN(1000)(double) { d =>
assertTrue(!d.isNaN || d.isNaN) // Always true
} &&
// Test all values from finite generator
checkAll(oneOf(1, 2, 3, 4, 5)) { n =>
assertTrue(n >= 1 && n <= 5)
}
}Generators for basic Scala types.
/** Boolean generator */
def boolean: Gen[Any, Boolean]
/** Byte generator with full range */
def byte: Gen[Any, Byte]
/** Byte generator within specified bounds */
def byteWith(min: Byte, max: Byte): Gen[Any, Byte]
/** Short generator with full range */
def short: Gen[Any, Short]
/** Short generator within specified bounds */
def shortWith(min: Short, max: Short): Gen[Any, Short]
/** Int generator with full range */
def int: Gen[Any, Int]
/** Int generator within specified bounds */
def intWith(min: Int, max: Int): Gen[Any, Int]
/** Long generator with full range */
def long: Gen[Any, Long]
/** Long generator within specified bounds */
def longWith(min: Long, max: Long): Gen[Any, Long]
/** Float generator */
def float: Gen[Any, Float]
/** Float generator within specified bounds */
def floatWith(min: Float, max: Float): Gen[Any, Float]
/** Double generator */
def double: Gen[Any, Double]
/** Double generator within specified bounds */
def doubleWith(min: Double, max: Double): Gen[Any, Double]
/** BigInt generator */
def bigInt: Gen[Any, BigInt]
/** BigInt generator within specified bounds */
def bigIntWith(min: BigInt, max: BigInt): Gen[Any, BigInt]
/** BigDecimal generator */
def bigDecimal: Gen[Any, BigDecimal]
/** BigDecimal generator within specified bounds */
def bigDecimalWith(min: BigDecimal, max: BigDecimal): Gen[Any, BigDecimal]
/** Java BigInteger generator */
def bigIntegerJava: Gen[Any, java.math.BigInteger]
/** Java BigDecimal generator */
def bigDecimalJava: Gen[Any, java.math.BigDecimal]Usage Examples:
import zio.test._
import zio.test.Gen._
test("primitive generators") {
check(boolean) { b =>
assertTrue(b == true || b == false)
} &&
check(intWith(1, 100)) { n =>
assertTrue(n >= 1 && n <= 100)
} &&
check(doubleWith(0.0, 1.0)) { d =>
assertTrue(d >= 0.0 && d <= 1.0)
}
}Generators for characters and strings with various constraints.
/** Character generator with full Unicode range */
def char: Gen[Any, Char]
/** Character generator within specified bounds */
def charWith(min: Char, max: Char): Gen[Any, Char]
/** Alphabetic character generator (a-z, A-Z) */
def alphaChar: Gen[Any, Char]
/** Alphanumeric character generator (a-z, A-Z, 0-9) */
def alphaNumericChar: Gen[Any, Char]
/** Numeric character generator (0-9) */
def numericChar: Gen[Any, Char]
/** ASCII character generator (0-127) */
def asciiChar: Gen[Any, Char]
/** Printable ASCII character generator */
def printableChar: Gen[Any, Char]
/** Hexadecimal character generator (0-9, a-f, A-F) */
def hexChar: Gen[Any, Char]
/** Lowercase hexadecimal character generator (0-9, a-f) */
def hexCharLower: Gen[Any, Char]
/** Uppercase hexadecimal character generator (0-9, A-F) */
def hexCharUpper: Gen[Any, Char]
/** Unicode character generator */
def unicodeChar: Gen[Any, Char]
/** Whitespace character generator */
def whitespaceChars: Gen[Any, Char]
/** String generator using printable characters */
def string: Gen[Any, String]
/** Non-empty string generator */
def string1: Gen[Any, String]
/** Fixed-length string generator */
def stringN(n: Int): Gen[Any, String]
/** Bounded-length string generator */
def stringBounded(min: Int, max: Int): Gen[Any, String]
/** Alphanumeric string generator */
def alphaNumericString: Gen[Any, String]
/** Bounded alphanumeric string generator */
def alphaNumericStringBounded(min: Int, max: Int): Gen[Any, String]
/** ASCII string generator */
def asciiString: Gen[Any, String]
/** ISO-8859-1 string generator */
def iso_8859_1: Gen[Any, String]Usage Examples:
import zio.test._
import zio.test.Gen._
test("character and string generators") {
check(alphaChar) { c =>
assertTrue(c.isLetter)
} &&
check(alphaNumericString) { s =>
assertTrue(s.forall(c => c.isLetterOrDigit))
} &&
check(stringBounded(5, 10)) { s =>
assertTrue(s.length >= 5 && s.length <= 10)
}
}Generators for various collection types.
/** List generator using another generator */
def listOf[A](gen: Gen[Any, A]): Gen[Any, List[A]]
/** Non-empty list generator */
def listOf1[A](gen: Gen[Any, A]): Gen[Any, List[A]]
/** Fixed-size list generator */
def listOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, List[A]]
/** Bounded-size list generator */
def listOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, List[A]]
/** Vector generator using another generator */
def vectorOf[A](gen: Gen[Any, A]): Gen[Any, Vector[A]]
/** Non-empty vector generator */
def vectorOf1[A](gen: Gen[Any, A]): Gen[Any, Vector[A]]
/** Fixed-size vector generator */
def vectorOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Vector[A]]
/** Bounded-size vector generator */
def vectorOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Vector[A]]
/** Chunk generator using another generator */
def chunkOf[A](gen: Gen[Any, A]): Gen[Any, Chunk[A]]
/** Non-empty chunk generator */
def chunkOf1[A](gen: Gen[Any, A]): Gen[Any, Chunk[A]]
/** Fixed-size chunk generator */
def chunkOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Chunk[A]]
/** Bounded-size chunk generator */
def chunkOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Chunk[A]]
/** Set generator using another generator */
def setOf[A](gen: Gen[Any, A]): Gen[Any, Set[A]]
/** Non-empty set generator */
def setOf1[A](gen: Gen[Any, A]): Gen[Any, Set[A]]
/** Fixed-size set generator */
def setOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Set[A]]
/** Bounded-size set generator */
def setOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Set[A]]
/** Map generator using key and value generators */
def mapOf[K, V](keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
/** Non-empty map generator */
def mapOf1[K, V](keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
/** Fixed-size map generator */
def mapOfN[K, V](n: Int)(keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
/** Bounded-size map generator */
def mapOfBounded[K, V](min: Int, max: Int)(keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]Usage Examples:
import zio.test._
import zio.test.Gen._
test("collection generators") {
check(listOf(int)) { numbers =>
assertTrue(numbers.forall(_.isInstanceOf[Int]))
} &&
check(listOfBounded(1, 5)(string)) { strings =>
assertTrue(strings.size >= 1 && strings.size <= 5)
} &&
check(setOf(intWith(1, 10))) { numbers =>
assertTrue(numbers.forall(n => n >= 1 && n <= 10))
} &&
check(mapOf(string, int)) { map =>
assertTrue(map.keys.forall(_.isInstanceOf[String]) &&
map.values.forall(_.isInstanceOf[Int]))
}
}Generators for Option and Either types.
/** Option generator that may produce Some or None */
def option[A](gen: Gen[Any, A]): Gen[Any, Option[A]]
/** Some generator that always produces Some */
def some[A](gen: Gen[Any, A]): Gen[Any, Some[A]]
/** None generator that always produces None */
def none: Gen[Any, None.type]
/** Either generator using left and right generators */
def either[A, B](leftGen: Gen[Any, A], rightGen: Gen[Any, B]): Gen[Any, Either[A, B]]Generators for specific use cases and combinations.
/** Constant value generator */
def const[A](value: A): Gen[Any, A]
/** Constant sample generator with shrinking */
def constSample[R, A](sample: Sample[R, A]): Gen[R, A]
/** Generator from specific elements */
def elements[A](as: A*): Gen[Any, A]
/** Generator choosing from multiple generators */
def oneOf[A](gens: Gen[Any, A]*): Gen[Any, A]
/** Weighted generator selection */
def weighted[A](weightedGens: (Gen[Any, A], Double)*): Gen[Any, A]
/** Generator from iterable collection */
def fromIterable[A](as: Iterable[A]): Gen[Any, A]
/** Generator from ZIO effect */
def fromZIO[R, A](zio: ZIO[R, Nothing, A]): Gen[R, A]
/** Generator from ZIO sample effect */
def fromZIOSample[R, A](zio: ZIO[R, Nothing, Sample[R, A]]): Gen[R, A]
/** Lazy generator evaluation */
def suspend[R, A](gen: => Gen[R, A]): Gen[R, A]
/** Empty generator (produces no values) */
def empty[A]: Gen[Any, A]
/** Unit generator */
def unit: Gen[Any, Unit]
/** UUID generator */
def uuid: Gen[Any, java.util.UUID]
/** Currency generator */
def currency: Gen[Any, java.util.Currency]
/** Throwable generator */
def throwable: Gen[Any, Throwable]Usage Examples:
import zio.test._
import zio.test.Gen._
test("special generators") {
check(const(42)) { n =>
assertTrue(n == 42)
} &&
check(elements("red", "green", "blue")) { color =>
assertTrue(Set("red", "green", "blue").contains(color))
} &&
check(oneOf(int, string.map(_.length))) { value =>
assertTrue(value.isInstanceOf[Int])
} &&
check(option(string)) { maybeString =>
assertTrue(maybeString.isEmpty || maybeString.get.isInstanceOf[String])
}
}Generators that work with size parameters.
/** Current size generator */
def size: Gen[Sized, Int]
/** Size-dependent generator */
def sized[R, A](f: Int => Gen[R, A]): Gen[R with Sized, A]
/** Small-sized generator (size / 4) */
def small[R, A](gen: Gen[R, A]): Gen[R, A]
/** Medium-sized generator (size / 2) */
def medium[R, A](gen: Gen[R, A]): Gen[R, A]
/** Large-sized generator (size * 2) */
def large[R, A](gen: Gen[R, A]): Gen[R, A]Generators for specific mathematical distributions.
/** Uniform distribution generator */
def uniform[A: Numeric](min: A, max: A): Gen[Any, A]
/** Exponential distribution generator */
def exponential[A: Numeric](lambda: A): Gen[Any, A]Functions for combining and transforming generators.
/** Collect all generator values into a collection */
def collectAll[R, A](gens: Iterable[Gen[R, A]]): Gen[R, List[A]]
/** Concatenate multiple generators */
def concatAll[R, A](gens: Iterable[Gen[R, A]]): Gen[R, A]Usage Examples:
import zio.test._
import zio.test.Gen._
test("size and distribution generators") {
check(sized(n => listOfN(n)(int))) { numbers =>
assertTrue(numbers.size >= 0)
} &&
check(uniform(1.0, 10.0)) { d =>
assertTrue(d >= 1.0 && d <= 10.0)
} &&
check(small(listOf(int))) { numbers =>
assertTrue(numbers.size <= 25) // Typically smaller due to size reduction
}
}