or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdindex.mdmocking.mdproperty-testing.mdtest-aspects.mdtest-environment.mdtest-execution.mdtest-specifications.md
tile.json

property-testing.mddocs/

Property Testing

Comprehensive property-based testing with generators, automatic shrinking, and configurable test execution.

Capabilities

Property Test Execution Functions

Functions for running property-based tests with different execution strategies.

/**
 * Tests a property with random samples from generator
 * @param rv - Generator for test values
 * @param test - Property test function
 * @returns Test result aggregating all sample results
 */
def check[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]

/**
 * Tests an effectful property with random samples
 * @param rv - Generator for test values
 * @param test - Effectful property test function
 * @returns ZIO effect producing aggregated test result
 */
def checkM[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

/**
 * Tests a property exhaustively with all samples from generator
 * @param rv - Generator for test values (should be finite)
 * @param test - Property test function
 * @returns Test result for all generated values
 */
def checkAll[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]

/**
 * Tests an effectful property exhaustively
 * @param rv - Generator for test values (should be finite)
 * @param test - Effectful property test function
 * @returns ZIO effect producing complete test result
 */
def checkAllM[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

/**
 * Tests a property with specific number of samples
 * @param n - Number of samples to generate
 * @returns CheckN instance for chaining with generator and test
 */
def checkN(n: Int): CheckVariants.CheckN

/**
 * Tests an effectful property with specific number of samples
 * @param n - Number of samples to generate
 * @returns CheckNM instance for chaining with generator and test
 */
def checkNM(n: Int): CheckVariants.CheckNM

Gen Class and Core Operations

The main generator class with monadic operations and transformations.

/**
 * A generator that produces values of type A in environment R
 * @tparam R - Environment requirements (usually TestConfig or Sized)
 * @tparam A - Type of generated values
 */
abstract class Gen[-R, +A] {
  /** Transform generated values with a function */
  def map[B](f: A => B): Gen[R, B]
  
  /** Monadic composition of generators */
  def flatMap[R1 <: R, B](f: A => Gen[R1, B]): Gen[R1, B]
  
  /** Filter generated values based on predicate */
  def filter(f: A => Boolean): Gen[R, A]
  
  /** Enable for-comprehension syntax */
  def withFilter(f: A => Boolean): Gen[R, A]
  
  /** Combine with another generator producing tuples */
  def zip[R1 <: R, B](that: Gen[R1, B]): Gen[R1, (A, B)]
  
  /** Combine generators with transformation function */
  def zipWith[R1 <: R, B, C](that: Gen[R1, B])(f: (A, B) => C): Gen[R1, C]
  
  /** Get stream of samples for testing */
  def sample: ZStream[R, Nothing, Sample[R, A]]
  
  /** Disable shrinking for this generator */
  def noShrink: Gen[R, A]
  
  /** Make generator produce optional values */
  def optional: Gen[R, Option[A]]
  
  /** Set specific size for generation */
  def resize(size: Int): Gen[R, A]
  
  /** Set size using an effect */
  def resizeM[R1 <: R](size: ZIO[R1, Nothing, Int]): Gen[R1, A]
}

Basic Value Generators

Fundamental generators for creating constant and computed values.

/** Generator that always produces unit value */
val unit: Gen[Any, Unit]

/** Generator that produces constant value */
def const[A](a: => A): Gen[Any, A]

/** Generator that produces pure value */
def succeed[A](a: A): Gen[Any, A]

/** Generator that produces lazy value */
def succeedLazy[A](a: => A): Gen[Any, A]

/** Generator that produces failure */
def fail[E](error: E): Gen[Any, E]

/** Generator that throws exception */
def failingWith[A](gen: Gen[Any, Throwable]): Gen[Any, A]

Primitive Type Generators

Built-in generators for primitive Scala types.

/** Boolean generator */
val boolean: Gen[Any, Boolean]

/** Byte generator with size-based range */
val byte: Gen[Sized, Byte]

/** Short generator with size-based range */
val short: Gen[Sized, Short]

/** Int generator with size-based range */
val int: Gen[Sized, Int]

/** Long generator with size-based range */
val long: Gen[Sized, Long]

/** Float generator */
val float: Gen[Sized, Float]

/** Double generator */
val double: Gen[Sized, Double]

/** BigInt generator */
val bigInt: Gen[Sized, BigInt]

/** BigDecimal generator */
val bigDecimal: Gen[Sized, BigDecimal]

/** Character generator */
val char: Gen[Any, Char]

/** String generator with size-based length */
val string: Gen[Sized, String]

Range-based Generators

Generators that produce values within specified ranges.

/** Int generator within range */
def int(min: Int, max: Int): Gen[Any, Int]

/** Long generator within range */
def long(min: Long, max: Long): Gen[Any, Long]

/** Double generator within range */
def double(min: Double, max: Double): Gen[Any, Double]

/** Float generator within range */
def float(min: Float, max: Float): Gen[Any, Float]

/** BigInt generator within range */
def bigInt(min: BigInt, max: BigInt): Gen[Any, BigInt]

/** BigDecimal generator within range */
def bigDecimal(min: BigDecimal, max: BigDecimal): Gen[Any, BigDecimal]

/** Character generator within range */
def char(min: Char, max: Char): Gen[Any, Char]

Character Generators

Specialized generators for different character sets.

/** Alphabetic characters (a-z, A-Z) */
val alphaChar: Gen[Any, Char]

/** Alphanumeric characters (a-z, A-Z, 0-9) */
val alphaNumericChar: Gen[Any, Char]

/** Lowercase alphabetic characters (a-z) */
val alphaLowerChar: Gen[Any, Char]

/** Uppercase alphabetic characters (A-Z) */
val alphaUpperChar: Gen[Any, Char]

/** ASCII characters */
val asciiChar: Gen[Any, Char]

/** Hexadecimal characters (0-9, a-f, A-F) */
val hexChar: Gen[Any, Char]

/** Numeric characters (0-9) */
val numericChar: Gen[Any, Char]

/** Printable characters */
val printableChar: Gen[Any, Char]

String Generators

Generators for strings with various constraints.

/** Non-empty string generator using character generator */
def string1(charGen: Gen[Any, Char]): Gen[Sized, String]

/** String generator using specific character generator */
def stringGen(charGen: Gen[Any, Char]): Gen[Sized, String]

/** String of fixed length */
def stringN(n: Int): Gen[Any, String]

/** String within length bounds */
def stringBounded(min: Int, max: Int): Gen[Any, String]

Collection Generators

Generators that produce collections of values.

/** List generator with size-based length */
def listOf[R, A](gen: Gen[R, A]): Gen[R with Sized, List[A]]

/** Non-empty list generator */
def listOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, ::[A]]

/** List generator with fixed size */
def listOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, List[A]]

/** List generator with bounded size */
def listOfBounded[R, A](min: Int, max: Int)(gen: Gen[R, A]): Gen[R, List[A]]

/** Vector generator with size-based length */
def vectorOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]

/** Non-empty vector generator */
def vectorOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]

/** Vector generator with fixed size */
def vectorOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Vector[A]]

/** Set generator with size-based cardinality */
def setOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]

/** Non-empty set generator */
def setOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]

/** Set generator with fixed size */
def setOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Set[A]]

/** Map generator with size-based cardinality */
def mapOf[R, A, B](keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R with Sized, Map[A, B]]

/** Non-empty map generator */
def mapOf1[R, A, B](keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R with Sized, Map[A, B]]

/** Map generator with fixed size */
def mapOfN[R, A, B](n: Int)(keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R, Map[A, B]]

Choice and Option Generators

Generators for selecting from alternatives and creating optional values.

/** Generator that chooses from multiple generators */
def oneOf[R, A](gen: Gen[R, A], gens: Gen[R, A]*): Gen[R, A]

/** Generator that chooses from values */
def elements[A](a: A, as: A*): Gen[Any, A]

/** Generator that chooses from collection */
def fromIterable[A](as: Iterable[A]): Gen[Any, A]

/** Weighted choice generator */
def weighted[R, A](gs: (Gen[R, A], Double)*): Gen[R, A]

/** None generator */
val none: Gen[Any, Option[Nothing]]

/** Some generator from another generator */
def some[R, A](gen: Gen[R, A]): Gen[R, Option[A]]

/** Optional generator (Some or None) */
def option[R, A](gen: Gen[R, A]): Gen[R, Option[A]]

Either Generators

Generators for Either values.

/** Either generator from left and right generators */
def either[R, A, B](left: Gen[R, A], right: Gen[R, B]): Gen[R, Either[A, B]]

/** Left generator */
def left[R, A](gen: Gen[R, A]): Gen[R, Either[A, Nothing]]

/** Right generator */
def right[R, A](gen: Gen[R, A]): Gen[R, Either[Nothing, A]]

Sample Class

Represents generated samples with shrinking capabilities.

/**
 * A generated sample that can be shrunk for failure minimization
 * @tparam R - Environment requirements
 * @tparam A - Type of sample value
 */
case class Sample[-R, +A](value: A, shrink: ZStream[R, Nothing, Sample[R, A]]) {
  /** Transform sample value */
  def map[B](f: A => B): Sample[R, B]
  
  /** Monadic composition */
  def flatMap[R1 <: R, B](f: A => Sample[R1, B]): Sample[R1, B]
  
  /** Filter sample */
  def filter(f: A => Boolean): Sample[R, A]
  
  /** Transform with effect */
  def foreach[R1 <: R, B](f: A => ZIO[R1, Nothing, B]): ZIO[R1, Nothing, Sample[R1, B]]
  
  /** Search for minimal failing sample */
  def shrinkSearch(f: A => Boolean): ZStream[R, Nothing, A]
}

Usage Examples

import zio.test._
import zio.test.Gen._

// Basic property test
test("string length property") {
  check(string) { s =>
    assert(s.reverse.reverse)(equalTo(s))
  }
}

// Multiple generators
test("addition property") {
  check(int, int) { (a, b) =>
    assert(a + b)(equalTo(b + a))
  }
}

// Custom generator
val evenInt = int.filter(_ % 2 == 0)

test("even numbers") {
  check(evenInt) { n =>
    assert(n % 2)(equalTo(0))
  }
}

// Collection properties
test("list concatenation") {
  check(listOf(int), listOf(int)) { (xs, ys) =>
    assert((xs ++ ys).length)(equalTo(xs.length + ys.length))
  }
}

// Effectful property test
testM("async property") {
  checkM(int) { n =>
    for {
      result <- someAsyncOperation(n)
    } yield assert(result)(isGreaterThan(n))
  }
}

// Exhaustive testing with finite generator
test("small numbers exhaustive") {
  checkAll(int(1, 10)) { n =>
    assert(n * n)(isGreaterThan(0))
  }
}

// Fixed number of samples
test("large sample test") {
  checkN(1000)(double) { d =>
    assert(d + 0.0)(equalTo(d))
  }
}