or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdcore-testing.mdindex.mdproperty-testing.mdsmart-assertions.mdtest-aspects.mdtest-services.md
tile.json

test-services.mddocs/

Test Services

Controllable test environment services that replace real services with deterministic, testable implementations. Test services enable predictable testing of time-dependent, I/O-dependent, and stateful code.

Capabilities

Test Environment

Core test environment type and management functions.

/**
 * Standard test environment combining all test services
 */
type TestEnvironment = Annotations with Live with Sized with TestConfig

object TestEnvironment {
  /** Layer providing TestEnvironment from existing environment */
  val any: ZLayer[TestEnvironment, Nothing, TestEnvironment]
  
  /** Layer providing TestEnvironment from live services */
  val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]
}

/**
 * Live environment layer providing real services
 */
val liveEnvironment: Layer[Nothing, Clock with Console with System with Random]

/**
 * Test environment layer providing all test services
 */
val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]

Usage Examples:

import zio.test._

// Using test environment in specs
object MyTest extends ZIOSpecDefault {
  def spec = suite("Test with environment")(
    test("time-based test") {
      for {
        _    <- TestClock.adjust(1.hour)
        time <- Clock.instant
      } yield assertTrue(time.getEpochSecond > 0)
    }
  )
}

// Custom environment combining test and domain services
type MyTestEnv = TestEnvironment with MyService

val myTestLayer: ZLayer[Any, Nothing, MyTestEnv] = 
  testEnvironment ++ MyService.test

TestClock Service

Controllable clock service for testing time-dependent operations.

/**
 * Controllable clock for testing time-dependent code
 */
trait TestClock extends Clock {
  /** Adjust the test clock by the specified duration */
  def adjust(duration: Duration)(implicit trace: Trace): UIO[Unit]
  
  /** Set the test clock to a specific instant */
  def setTime(instant: Instant)(implicit trace: Trace): UIO[Unit]
  
  /** Get all pending scheduled operations */
  def sleeps(implicit trace: Trace): UIO[List[Duration]]
  
  /** Save the current clock state */
  def save(implicit trace: Trace): UIO[TestClock.Data]
  
  /** Restore the clock to a previous state */
  def restore(data: TestClock.Data)(implicit trace: Trace): UIO[Unit]
}

object TestClock {
  /** Default TestClock layer */
  val default: ZLayer[Any, Nothing, TestClock]
  
  /** Data representing clock state for save/restore */
  case class Data(instant: Instant, sleeps: List[Duration])
}

/**
 * Access TestClock service
 */
def testClock(implicit trace: Trace): UIO[TestClock]

/**
 * Access TestClock service and run workflow
 */
def testClockWith[R, E, A](f: TestClock => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Usage Examples:

import zio.test._

test("time-dependent operations") {
  for {
    // Start a timed operation
    fiber <- ZIO.sleep(1.hour).fork
    
    // Verify it hasn't completed yet
    isDone <- fiber.isDone
    _      <- assertTrue(!isDone)
    
    // Advance time
    _      <- TestClock.adjust(1.hour)
    
    // Verify operation completed
    _      <- fiber.join
    isDone <- fiber.isDone
  } yield assertTrue(isDone)
}

test("clock manipulation") {
  for {
    // Set specific time
    _ <- TestClock.setTime(Instant.ofEpochSecond(0))
    
    // Check current time
    now <- Clock.instant
    _   <- assertTrue(now.getEpochSecond == 0)
    
    // Advance time by specific amount
    _ <- TestClock.adjust(Duration.ofMinutes(30))
    
    // Verify time advanced
    later <- Clock.instant  
  } yield assertTrue(later.getEpochSecond == 30 * 60)
}

TestConsole Service

Controllable console service for testing I/O operations.

/**
 * Controllable console for testing I/O operations
 */
trait TestConsole extends Console {
  /** Feed input to the console for reading */
  def feedLines(lines: String*)(implicit trace: Trace): UIO[Unit]
  
  /** Get all output written to console */
  def output(implicit trace: Trace): UIO[Vector[String]]
  
  /** Clear all console output */
  def clearOutput(implicit trace: Trace): UIO[Unit]
  
  /** Get all error output written to console */
  def errorOutput(implicit trace: Trace): UIO[Vector[String]]
  
  /** Clear all console error output */
  def clearErrorOutput(implicit trace: Trace): UIO[Unit]
  
  /** Save the current console state */
  def save(implicit trace: Trace): UIO[TestConsole.Data]
  
  /** Restore console to previous state */
  def restore(data: TestConsole.Data)(implicit trace: Trace): UIO[Unit]
}

object TestConsole {
  /** Default TestConsole layer */
  val default: ZLayer[Any, Nothing, TestConsole]
  
  /** Debug TestConsole layer that prints to real console */
  val debug: ZLayer[Any, Nothing, TestConsole]
  
  /** Data representing console state for save/restore */
  case class Data(input: List[String], output: Vector[String], errorOutput: Vector[String])
}

/**
 * Access TestConsole service
 */
def testConsole(implicit trace: Trace): UIO[TestConsole]

/**
 * Access TestConsole service and run workflow
 */
def testConsoleWith[R, E, A](f: TestConsole => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Usage Examples:

import zio.test._

test("console input/output") {
  for {
    // Feed input for reading
    _ <- TestConsole.feedLines("Alice", "25")
    
    // Read input 
    name <- Console.readLine("Enter name: ")
    age  <- Console.readLine("Enter age: ")
    
    // Write output
    _ <- Console.printLine(s"Hello $name, you are $age years old")
    
    // Verify output
    output <- TestConsole.output
  } yield assertTrue(
    name == "Alice" &&
    age == "25" &&
    output.contains("Hello Alice, you are 25 years old")
  )
}

test("error output") {
  for {
    // Write to error stream
    _ <- Console.printLineError("Error occurred")
    
    // Check error output
    errors <- TestConsole.errorOutput
  } yield assertTrue(errors.contains("Error occurred"))
}

TestRandom Service

Controllable random number generator for deterministic testing.

/**
 * Controllable random number generator for deterministic testing
 */
trait TestRandom extends Random {
  /** Set the random seed */
  def setSeed(seed: Long)(implicit trace: Trace): UIO[Unit]
  
  /** Get the current random seed */
  def getSeed(implicit trace: Trace): UIO[Long]
  
  /** Feed specific values to be returned by random operations */
  def feedInts(ints: Int*)(implicit trace: Trace): UIO[Unit]
  def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit] 
  def feedDoubles(doubles: Double*)(implicit trace: Trace): UIO[Unit]
  def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit]
  def feedBooleans(booleans: Boolean*)(implicit trace: Trace): UIO[Unit]
  def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit]
  def feedUUIDs(uuids: java.util.UUID*)(implicit trace: Trace): UIO[Unit]
  def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit]
  
  /** Clear all fed values */
  def clearInts(implicit trace: Trace): UIO[Unit]
  def clearLongs(implicit trace: Trace): UIO[Unit]
  def clearDoubles(implicit trace: Trace): UIO[Unit]
  def clearFloats(implicit trace: Trace): UIO[Unit]
  def clearBooleans(implicit trace: Trace): UIO[Unit]
  def clearStrings(implicit trace: Trace): UIO[Unit]
  def clearUUIDs(implicit trace: Trace): UIO[Unit]
  def clearBytes(implicit trace: Trace): UIO[Unit]
  
  /** Save the current random state */
  def save(implicit trace: Trace): UIO[TestRandom.Data]
  
  /** Restore random to previous state */
  def restore(data: TestRandom.Data)(implicit trace: Trace): UIO[Unit]
}

object TestRandom {
  /** Deterministic TestRandom layer */
  val deterministic: ZLayer[Any, Nothing, TestRandom]
  
  /** TestRandom layer with specific seed */
  def seeded(seed: Long): ZLayer[Any, Nothing, TestRandom]
  
  /** Data representing random state for save/restore */
  case class Data(seed: Long, ints: List[Int], /* other fed values */)
}

/**
 * Access TestRandom service
 */
def testRandom(implicit trace: Trace): UIO[TestRandom]

/**
 * Access TestRandom service and run workflow  
 */
def testRandomWith[R, E, A](f: TestRandom => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Usage Examples:

import zio.test._

test("deterministic random values") {
  for {
    // Feed specific values
    _ <- TestRandom.feedInts(42, 24, 7)
    _ <- TestRandom.feedBooleans(true, false, true)
    
    // Generate values (will use fed values)
    n1 <- Random.nextInt
    n2 <- Random.nextInt  
    n3 <- Random.nextInt
    
    b1 <- Random.nextBoolean
    b2 <- Random.nextBoolean
    b3 <- Random.nextBoolean
  } yield assertTrue(
    n1 == 42 && n2 == 24 && n3 == 7 &&
    b1 && !b2 && b3
  )
}

test("seeded random") {
  for {
    // Set specific seed for reproducible tests
    _ <- TestRandom.setSeed(12345L)
    
    // Generate values (will be deterministic)
    values <- ZIO.collectAll(List.fill(5)(Random.nextInt))
    
    // Reset to same seed
    _ <- TestRandom.setSeed(12345L)
    
    // Generate same values again
    sameValues <- ZIO.collectAll(List.fill(5)(Random.nextInt))
  } yield assertTrue(values == sameValues)
}

TestSystem Service

Controllable system environment for testing system interactions.

/**
 * Controllable system environment for testing system interactions
 */
trait TestSystem extends System {
  /** Set environment variable */
  def putEnv(name: String, value: String)(implicit trace: Trace): UIO[Unit]
  
  /** Remove environment variable */
  def clearEnv(name: String)(implicit trace: Trace): UIO[Unit]
  
  /** Set system property */
  def putProperty(name: String, value: String)(implicit trace: Trace): UIO[Unit]
  
  /** Remove system property */
  def clearProperty(name: String)(implicit trace: Trace): UIO[Unit]
  
  /** Set all environment variables */
  def setEnvs(envs: Map[String, String])(implicit trace: Trace): UIO[Unit]
  
  /** Set all system properties */
  def setProperties(props: Map[String, String])(implicit trace: Trace): UIO[Unit]
  
  /** Save the current system state */
  def save(implicit trace: Trace): UIO[TestSystem.Data]
  
  /** Restore system to previous state */
  def restore(data: TestSystem.Data)(implicit trace: Trace): UIO[Unit]
}

object TestSystem {
  /** Default TestSystem layer */
  val default: ZLayer[Any, Nothing, TestSystem]
  
  /** Data representing system state for save/restore */
  case class Data(envs: Map[String, String], properties: Map[String, String])
}

/**
 * Access TestSystem service
 */
def testSystem(implicit trace: Trace): UIO[TestSystem]

/**
 * Access TestSystem service and run workflow
 */
def testSystemWith[R, E, A](f: TestSystem => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Usage Examples:

import zio.test._

test("environment variables") {
  for {
    // Set environment variable
    _ <- TestSystem.putEnv("MY_VAR", "test_value")
    
    // Read environment variable
    value <- System.env("MY_VAR")
    
    // Clear environment variable
    _ <- TestSystem.clearEnv("MY_VAR")
    
    // Verify it's gone
    cleared <- System.env("MY_VAR")
  } yield assertTrue(
    value.contains("test_value") &&
    cleared.isEmpty
  )
}

test("system properties") {
  for {
    // Set system property
    _ <- TestSystem.putProperty("test.prop", "test_value")
    
    // Read system property
    value <- System.property("test.prop")
    
    // Clear system property
    _ <- TestSystem.clearProperty("test.prop")
    
    // Verify it's gone
    cleared <- System.property("test.prop")
  } yield assertTrue(
    value.contains("test_value") &&
    cleared.isEmpty
  )
}

TestConfig Service

Test configuration service for controlling test execution parameters.

/**
 * Test configuration service
 */
trait TestConfig {
  /** Number of samples for property-based tests */
  def samples: Int
  
  /** Maximum number of shrinking iterations */
  def shrinks: Int
  
  /** Repeats configuration */
  def repeats: Int
  
  /** Retries configuration */
  def retries: Int
  
  /** Test aspect to apply to property tests */
  def checkAspect: TestAspectPoly
}

object TestConfig {
  /** Live TestConfig layer with specified parameters */
  def live(samples: Int, shrinks: Int, repeats: Int, retries: Int): ZLayer[Any, Nothing, TestConfig]
  
  /** Default TestConfig layer */
  val default: ZLayer[Any, Nothing, TestConfig]
}

/**
 * Access TestConfig service
 */
def testConfig(implicit trace: Trace): UIO[TestConfig]

/**
 * Access TestConfig service and run workflow
 */
def testConfigWith[R, E, A](f: TestConfig => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Annotations Service

Service for managing test annotations and metadata.

/**
 * Service for managing test annotations and metadata
 */
trait Annotations {
  /** Get annotation value for the specified key */
  def get[V](key: TestAnnotation[V])(implicit trace: Trace): UIO[V]
  
  /** Annotate with key-value pair */
  def annotate[V](key: TestAnnotation[V], value: V)(implicit trace: Trace): UIO[Unit]
  
  /** Get all annotations as a map */
  def annotated(implicit trace: Trace): UIO[TestAnnotationMap]
  
  /** Run effect with additional annotation */
  def withAnnotation[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
}

object Annotations {
  /** Live Annotations layer */
  val live: ZLayer[Any, Nothing, Annotations]
}

/**
 * Access Annotations service
 */
def annotations(implicit trace: Trace): UIO[Annotations]

/**
 * Access Annotations service and run workflow
 */
def annotationsWith[R, E, A](f: Annotations => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Sized Service

Service providing size parameter for generators.

/**
 * Service providing size parameter for generators
 */
trait Sized {
  /** Get the current size parameter */
  def size(implicit trace: Trace): UIO[Int]
}

object Sized {
  /** Live Sized layer with specified size */
  def live(size: Int): ZLayer[Any, Nothing, Sized]
}

/**
 * Access Sized service
 */
def sized(implicit trace: Trace): UIO[Sized]

/**
 * Access Sized service and run workflow
 */
def sizedWith[R, E, A](f: Sized => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

Live Service

Service for accessing live environment during tests.

/**
 * Service for accessing live environment during tests
 */
trait Live {
  /** Run effect with live environment instead of test environment */
  def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
}

object Live {
  /** Default Live layer */
  val default: ZLayer[Any, Nothing, Live]
}

/**
 * Access Live service
 */
def live(implicit trace: Trace): UIO[Live]

/**
 * Access Live service and run workflow  
 */
def liveWith[R, E, A](f: Live => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

/**
 * Run effect with live environment
 */
def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

/**
 * Transform effect with live environment access
 */
def withLive[R, E, E1, A, B](zio: ZIO[R, E, A])(f: ZIO[R, E, A] => ZIO[R, E1, B])(implicit trace: Trace): ZIO[R, E1, B]

Service Management Functions

Functions for managing and customizing test services.

/**
 * Run workflow with custom annotations service
 */
def withAnnotations[R, E, A <: Annotations, B](annotations: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

/**
 * Set annotations service in scope
 */
def withAnnotationsScoped[A <: Annotations](annotations: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

/**
 * Run workflow with custom config service
 */
def withTestConfig[R, E, A <: TestConfig, B](testConfig: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

/**
 * Set config service in scope
 */
def withTestConfigScoped[A <: TestConfig](testConfig: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

/**
 * Run workflow with custom sized service
 */
def withSized[R, E, A <: Sized, B](sized: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

/**
 * Set sized service in scope
 */
def withSizedScoped[A <: Sized](sized: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

/**
 * Run workflow with custom live service
 */
def withLive[R, E, A <: Live, B](live: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

/**
 * Set live service in scope
 */
def withLiveScoped[A <: Live](live: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

Usage Examples:

import zio.test._

test("custom test configuration") {
  val customConfig = new TestConfig {
    def samples = 1000  // More samples than default
    def shrinks = 50    // More shrinking iterations
    def repeats = 1
    def retries = 1
    def checkAspect = TestAspect.identity
  }
  
  withTestConfig(customConfig) {
    check(Gen.int) { n =>
      assertTrue(n.isInstanceOf[Int])
    }
  }
}

test("custom size parameter") {
  val largeSize = new Sized {
    def size(implicit trace: Trace) = ZIO.succeed(200)
  }
  
  withSized(largeSize) {
    check(Gen.sized(n => Gen.listOfN(n)(Gen.int))) { numbers =>
      assertTrue(numbers.size <= 200)
    }
  }
}