ZIO Test is a zero-dependency testing library that makes it easy to test effectual programs
npx @tessl/cli install tessl/maven-dev-zio--zio-test@2.1.0ZIO Test is a comprehensive testing framework for effectual Scala programs built with ZIO. It provides zero-dependency testing with advanced features including property-based testing, sophisticated assertions, test aspects for cross-cutting concerns, and deterministic test services for reliable testing of concurrent and asynchronous code.
libraryDependencies += "dev.zio" %% "zio-test" % "2.1.19"libraryDependencies += "dev.zio" %% "zio-test-sbt" % "2.1.19" % Testimport zio.test._
import zio.test.Assertion._
import zio.test.Gen._For specific functionality:
// Property-based testing
import zio.test.{Gen, check, checkAll}
// Test aspects
import zio.test.TestAspect.{timeout, parallel, eventually}
// Test services
import zio.test.{TestClock, TestConsole, TestRandom, TestSystem}
// Advanced features
import zio.test.laws._ // Laws-based property testing
import zio.test.diff._ // Value differencing system
import zio.test.render._ // Test result rendering
import zio.test.poly._ // Polymorphic generatorsimport zio.test._
import zio.test.Assertion._
object BasicExampleSpec extends ZIOSpecDefault {
def spec = suite("Basic Examples")(
test("simple assertion") {
val result = 2 + 2
assertTrue(result == 4)
},
test("ZIO effect testing") {
for {
time <- zio.Clock.currentTime(java.util.concurrent.TimeUnit.MILLISECONDS)
} yield assertTrue(time > 0L)
},
test("property-based testing") {
check(Gen.int) { n =>
assertTrue((n + n) == (n * 2))
}
}
)
}ZIO Test is built around several key components:
test, suite, ZTest for creating test specificationsTestClock, TestConsole, TestRandom, TestSystem for predictable testingCore DSL for creating tests and test suites with support for nested suites, test organization, and ZIO effect integration.
def test[R](label: String)(assertion: => ZIO[R, Any, TestResult]): Spec[R, Any]
def suite[R](label: String)(specs: Spec[R, Any]*): Spec[R, Any]
trait Spec[+R, +E] {
def @@[R1 <: R](aspect: TestAspect[Nothing, R1, Nothing, Any]): Spec[R1, E]
}
type ZTest[-R, +E] = ZIO[R, TestFailure[E], TestSuccess]
// Base classes for test specifications
abstract class ZIOSpec[R] {
def spec: Spec[R, Any]
}
abstract class ZIOSpecDefault extends ZIOSpec[TestEnvironment] {
def spec: Spec[TestEnvironment, Any]
}
abstract class ZIOSpecAbstract[R](implicit trace: Trace) extends ZIOSpec[R] {
def spec: Spec[R, Any]
}Comprehensive assertion library with combinable assertions, smart error messages, and detailed failure reporting.
def assertTrue(exprs: Boolean*): TestResult
def assert[A](expr: => A)(assertion: Assertion[A]): TestResult
def assertZIO[R, E, A](effect: ZIO[R, E, A])(assertion: Assertion[A]): ZIO[R, E, TestResult]
trait Assertion[-A] {
def apply(value: A): TestResult
def &&(that: Assertion[A]): Assertion[A]
def ||(that: Assertion[A]): Assertion[A]
def unary_! : Assertion[A]
}
// Core assertion functions
def equalTo[A](expected: A): Assertion[A]
def isTrue: Assertion[Boolean]
def contains[A](element: A): Assertion[Iterable[A]]
def hasSize[A](assertion: Assertion[Int]): Assertion[Iterable[A]]Advanced property-based testing with sophisticated generators, automatic shrinking, and configurable test execution.
def check[R <: ZAny, A](rv: Gen[R, A])(test: A => TestResult): ZIO[R with TestConfig, Nothing, TestResult]
def checkAll[R <: ZAny, A](rv: Gen[R, A])(test: A => TestResult): ZIO[R with TestConfig, Nothing, TestResult]
trait Gen[+R, +A] {
def map[B](f: A => B): Gen[R, B]
def flatMap[R1 <: R, B](f: A => Gen[R1, B]): Gen[R1, B]
def filter(f: A => Boolean): Gen[R, A]
def sample: ZStream[R, Nothing, Sample[R, A]]
}
// Generator constructors
val anyInt: Gen[Any, Int]
val anyString: Gen[Any, String]
def listOfN[R, A](n: Int)(g: Gen[R, A]): Gen[R, List[A]]
def oneOf[R, A](first: Gen[R, A], rest: Gen[R, A]*): Gen[R, A]Cross-cutting concerns for test execution including timeouts, retries, parallelism, and platform-specific behavior.
trait TestAspect[-LowerR, +UpperR, -LowerE, +UpperE] {
def apply[R >: LowerR <: UpperR, E >: LowerE <: UpperE](
spec: Spec[R, E]
): Spec[R, E]
}
// Common test aspects
object TestAspect {
def timeout(duration: Duration): TestAspectAtLeastR[Live]
val parallel: TestAspectPoly
val sequential: TestAspectPoly
val eventually: TestAspectAtLeastR[Live]
val flaky: TestAspectPoly
val ignore: TestAspectPoly
val js: TestAspectPoly
val jvm: TestAspectPoly
}Deterministic test services that replace system services during testing for predictable, reproducible test execution.
trait TestClock extends Clock {
def adjust(duration: Duration): UIO[Unit]
def setTime(duration: Duration): UIO[Unit]
def timeZone: UIO[ZoneId]
}
trait TestConsole extends Console {
def output: UIO[Vector[String]]
def clearOutput: UIO[Unit]
def feedLines(lines: String*): UIO[Unit]
}
trait TestRandom extends Random {
def setSeed(seed: Long): UIO[Unit]
def feedBooleans(booleans: Boolean*): UIO[Unit]
def clearBooleans: UIO[Unit]
}
trait TestSystem extends System {
def putEnv(name: String, value: String): UIO[Unit]
def putProperty(name: String, value: String): UIO[Unit]
}Configuration system for controlling test execution parameters and environment setup for dependency injection.
trait TestConfig {
def repeats: Int
def retries: Int
def samples: Int
def shrinks: Int
}
type TestEnvironment = Annotations with Live with Sized with TestConfig
object TestEnvironment {
val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]
}
val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]Advanced property-based testing using mathematical laws and algebraic properties for typeclass verification.
trait Laws[Caps, R] {
def laws: List[ZLaws[Caps, R]]
}
trait ZLaws[-Caps, -R] {
def run[R1 <: R, A](implicit
checkConstructor: CheckConstructor[R1],
caps: Caps,
gen: GenF[R1, A]
): URIO[R1, TestResult]
}
object Laws {
def derive[F[_], Caps, R](implicit
laws: Laws[Caps, R]
): ZLaws[Caps, R]
}System for computing and rendering differences between values for enhanced test failure reporting.
trait Diff[A] {
def apply(x: A, y: A): DiffResult
}
sealed trait DiffResult {
def render: String
def isIdentical: Boolean
}
object Diff {
val string: Diff[String]
val boolean: Diff[Boolean]
val int: Diff[Int]
def option[A](implicit diff: Diff[A]): Diff[Option[A]]
def either[A, B](implicit diffA: Diff[A], diffB: Diff[B]): Diff[Either[A, B]]
}System for running tests and collecting results with support for various execution strategies and event handling.
trait TestRunner[R, E] {
def run(spec: Spec[R, E]): ZIO[R, Nothing, Summary]
}
trait TestExecutor[R, E] {
def run(spec: Spec[R, E], defExec: ExecutionStrategy): ZIO[R, Nothing, ExecutionResult[E]]
}
object TestRunner {
def default[R, E]: TestRunner[R, E]
}
// Execution events
sealed trait ExecutionEvent
case class SuiteStarted(labels: List[String]) extends ExecutionEvent
case class SuiteCompleted(labels: List[String]) extends ExecutionEvent
case class TestStarted(labels: List[String]) extends ExecutionEvent
case class TestCompleted(labels: List[String], result: TestResult) extends ExecutionEvent
// Test results and summaries
case class Summary(
success: Int,
fail: Int,
ignore: Int,
failureDetails: String
) {
def total: Int = success + fail + ignore
}
sealed trait ExecutionStrategy
object ExecutionStrategy {
case object Sequential extends ExecutionStrategy
case object Parallel extends ExecutionStrategy
}// Core test types
trait TestResult {
def isSuccess: Boolean
def isFailure: Boolean
def ??(message: String): TestResult
}
sealed trait TestFailure[+E] {
def &&[E1 >: E](that: TestFailure[E1]): TestFailure[E1]
def ||[E1 >: E](that: TestFailure[E1]): TestFailure[E1]
def unary_! : TestFailure[E]
}
case class TestSuccess() extends AnyVal
// Diff system types
trait Diff[A] {
def apply(x: A, y: A): DiffResult
}
sealed trait DiffResult {
def render: String
}
// Test trace for debugging
case class TestTrace(
rendered: String,
span: Span
)
// Test structure types
trait Spec[+R, +E]
case class SpecCase[+R, +E](
label: String,
test: ZIO[R, TestFailure[E], TestSuccess],
annotations: TestAnnotationMap
)
// Sample and generation types
trait Sample[+R, +A] {
def value: A
def shrink: ZStream[R, Nothing, Sample[R, A]]
}
case class Sized(size: Int) extends AnyVal