or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdconfiguration.mdindex.mdproperty-testing.mdtest-aspects.mdtest-definition.mdtest-services.md
tile.json

configuration.mddocs/

Configuration and Environment

Configuration system for controlling test execution parameters and environment setup for dependency injection.

Capabilities

TestConfig

Configuration service that controls various aspects of test execution.

/**
 * Configuration for test execution parameters
 */
trait TestConfig {
  /**
   * Number of times to repeat each test
   * @return number of repetitions
   */
  def repeats: Int
  
  /**
   * Number of times to retry failed tests
   * @return number of retries
   */
  def retries: Int
  
  /**
   * Number of samples for property-based tests
   * @return number of samples to generate
   */
  def samples: Int
  
  /**
   * Maximum shrinking attempts for failed property tests
   * @return maximum shrinking attempts
   */
  def shrinks: Int
  
  /**
   * Size parameter for generators
   * @return size value for Sized environment
   */
  def size: Int
}

object TestConfig {
  /**
   * Create test configuration with specified parameters
   * @param repeats number of test repetitions
   * @param retries number of retry attempts
   * @param samples number of property test samples
   * @param shrinks maximum shrinking attempts
   * @param size generator size parameter
   * @return layer providing test configuration
   */
  def live(
    repeats: Int,
    retries: Int, 
    samples: Int,
    shrinks: Int,
    size: Int
  ): ULayer[TestConfig]
  
  /**
   * Default test configuration
   * - repeats: 1
   * - retries: 0
   * - samples: 200
   * - shrinks: 1000
   * - size: 100
   */
  val default: ULayer[TestConfig]
  
  /**
   * Access number of repeats from configuration
   */
  val repeats: URIO[TestConfig, Int]
  
  /**
   * Access number of retries from configuration
   */
  val retries: URIO[TestConfig, Int]
  
  /**
   * Access number of samples from configuration
   */
  val samples: URIO[TestConfig, Int]
  
  /**
   * Access number of shrinks from configuration
   */
  val shrinks: URIO[TestConfig, Int]
  
  /**
   * Access size parameter from configuration
   */
  val size: URIO[TestConfig, Int]
}

Usage Examples:

import zio.test._
import zio._

// Custom test configuration
val customConfig = TestConfig.live(
  repeats = 5,      // Run each test 5 times
  retries = 2,      // Retry failed tests twice
  samples = 1000,   // Use 1000 samples for property tests
  shrinks = 500,    // Maximum 500 shrinking attempts
  size = 200        // Generator size of 200
)

// Using configuration in tests
test("property test with custom config").provideLayer(customConfig) {
  check(Gen.listOf(Gen.anyInt)) { list =>
    assertTrue(list.reverse.reverse == list)
  }
}

// Access configuration values
test("configuration access") {
  for {
    samples <- TestConfig.samples
    size <- TestConfig.size
  } yield assertTrue(samples > 0 && size > 0)
}

TestEnvironment

Combined environment type containing all standard test services.

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

object TestEnvironment {
  /**
   * Create test environment from live system services
   * @return layer providing complete test environment
   */
  val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]
  
  /**
   * Access any service from test environment
   * @return layer that provides access to the environment
   */
  val any: ZLayer[TestEnvironment, Nothing, TestEnvironment]
}

/**
 * Layer providing live system services for test environment creation
 */
val liveEnvironment: Layer[Nothing, Clock with Console with System with Random]

/**
 * Complete test environment layer (recommended for most tests)
 */
val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]

Usage Examples:

import zio.test._
import zio._

// Using default test environment
object MyTestSpec extends ZIOSpecDefault {
  def spec = suite("My Tests")(
    test("basic test") {
      // Has access to full TestEnvironment
      for {
        time <- Clock.currentTime(TimeUnit.MILLISECONDS)
        _ <- Console.printLine(s"Current time: $time")
        random <- Random.nextInt(100)
      } yield assertTrue(time > 0 && random >= 0 && random < 100)
    }
  )
}

// Custom environment combination
val customTestEnv: ZLayer[Any, Nothing, TestEnvironment] = 
  ZLayer.make[TestEnvironment](
    TestClock.default,
    TestConsole.debug,
    TestRandom.deterministic(12345L),
    TestSystem.default,
    Annotations.live,
    Live.default,
    Sized.live(50),
    TestConfig.live(1, 0, 500, 1000, 50)
  )

object CustomEnvSpec extends ZIOSpec[TestEnvironment] {
  def spec = suite("Custom Environment Tests")(
    test("uses custom environment") {
      for {
        size <- Sized.size
        samples <- TestConfig.samples
      } yield assertTrue(size == 50 && samples == 500)
    }
  ).provideLayer(customTestEnv)
}

TestArgs

Command-line argument parsing for test runners.

/**
 * Command-line arguments for test execution
 */
case class TestArgs(
  testSearchTerms: List[String] = Nil,
  tagSearchTerms: List[String] = Nil,
  testTaskPolicy: TestTaskPolicy = TestTaskPolicy.Sequential,
  
  // Execution parameters
  repeats: Option[Int] = None,
  retries: Option[Int] = None, 
  samples: Option[Int] = None,
  shrinks: Option[Int] = None,
  size: Option[Int] = None,
  
  // Output control
  verbose: Boolean = false,
  color: Boolean = true,
  summary: Boolean = true
)

object TestArgs {
  /**
   * Parse command-line arguments
   * @param args command-line argument list
   * @return parsed test arguments
   */
  def parse(args: List[String]): Either[String, TestArgs]
  
  /**
   * Parse command-line arguments with defaults
   * @param args command-line argument list  
   * @param defaults default test arguments
   * @return parsed test arguments
   */
  def parse(args: List[String], defaults: TestArgs): Either[String, TestArgs]
  
  /**
   * Empty test arguments (all defaults)
   */
  val empty: TestArgs
}

/**
 * Test execution policy
 */
sealed trait TestTaskPolicy
object TestTaskPolicy {
  case object Sequential extends TestTaskPolicy
  case object Parallel extends TestTaskPolicy
  case class ParallelN(n: Int) extends TestTaskPolicy
}

Usage Examples:

import zio.test._

// Parsing command-line arguments
val args = List("--samples", "1000", "--parallel", "--verbose", "UserTests")
val testArgs = TestArgs.parse(args) match {
  case Right(args) => args
  case Left(error) => throw new IllegalArgumentException(error)
}

// Using parsed arguments to configure tests
val configFromArgs = testArgs.samples.fold(TestConfig.default) { samples =>
  TestConfig.live(
    repeats = testArgs.repeats.getOrElse(1),
    retries = testArgs.retries.getOrElse(0),
    samples = samples,
    shrinks = testArgs.shrinks.getOrElse(1000),
    size = testArgs.size.getOrElse(100)
  )
}

Annotations

Service for attaching metadata to tests.

/**
 * Service for managing test annotations and metadata
 */
trait Annotations {
  /**
   * Get annotation value by key
   * @param key annotation key
   * @return effect producing optional annotation value
   */
  def get[V](key: TestAnnotation[V]): UIO[V]
  
  /**
   * Annotate with key-value pair
   * @param key annotation key
   * @param value annotation value
   * @return effect that adds the annotation
   */
  def annotate[V](key: TestAnnotation[V], value: V): UIO[Unit]
  
  /**
   * Get all current annotations
   * @return effect producing annotation map
   */
  def annotationMap: UIO[TestAnnotationMap]
  
  /**
   * Supervise effect with annotation inheritance
   * @param zio effect to supervise
   * @return supervised effect with annotations
   */
  def supervisedFibers[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
}

object Annotations {
  /**
   * Live annotations implementation
   */
  val live: ULayer[Annotations]
}

/**
 * Test annotation key-value pair
 */
trait TestAnnotation[V] {
  /**
   * Annotation identifier
   */
  def identifier: String
  
  /**
   * Initial/default value
   */
  def initial: V
  
  /**
   * Combine two annotation values
   * @param v1 first value
   * @param v2 second value
   * @return combined value
   */
  def combine(v1: V, v2: V): V
}

object TestAnnotation {
  /**
   * Create custom annotation
   * @param name annotation name
   * @param initialValue initial value
   * @param combineFunction value combination function
   * @return annotation instance
   */
  def apply[V](
    name: String,
    initialValue: V,
    combineFunction: (V, V) => V
  ): TestAnnotation[V]
  
  // Built-in annotations
  val ignored: TestAnnotation[Boolean]
  val repeated: TestAnnotation[Int]
  val retried: TestAnnotation[Int]
  val tagged: TestAnnotation[Set[String]]
  val timing: TestAnnotation[Duration]
}

Usage Examples:

import zio.test._
import zio._

// Using built-in annotations
test("annotated test") {
  for {
    _ <- Annotations.annotate(TestAnnotation.tagged, Set("slow", "integration"))
    _ <- Annotations.annotate(TestAnnotation.repeated, 5)
    annotations <- Annotations.annotationMap
  } yield assertTrue(annotations.get(TestAnnotation.tagged).contains("slow"))
}

// Custom annotations
val databaseAnnotation = TestAnnotation[String](
  "database",
  "none", 
  (_, newer) => newer // Use newer value
)

test("database test") {
  for {
    _ <- Annotations.annotate(databaseAnnotation, "postgresql")
    dbType <- Annotations.get(databaseAnnotation)
  } yield assertTrue(dbType == "postgresql")
}

Live Service

Service for executing effects with live (production) implementations.

/**
 * Service for accessing live implementations of system services
 */
trait Live {
  /**
   * Execute effect with live service implementations
   * @param zio effect to execute with live services
   * @return effect executed with live services
   */
  def provide[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
}

object Live {
  /**
   * Default live service implementation
   */
  val default: ULayer[Live]
  
  /**
   * Execute effect with live services (convenience function)
   * @param zio effect to execute
   * @return effect with live services
   */
  def live[R, E, A](zio: ZIO[R, E, A]): ZIO[R with Live, E, A]
  
  /**
   * Transform effect to use live services for outer effect while preserving
   * test environment for inner effect
   * @param zio inner effect using test environment
   * @param f transformation using live environment
   * @return transformed effect
   */
  def withLive[R, E, E1, A, B](zio: ZIO[R, E, A])(
    f: ZIO[R, E, A] => ZIO[R, E1, B]
  ): ZIO[R with Live, E1, B]
}

Usage Examples:

import zio.test._
import zio._

// Execute with live services when needed
test("performance test with real time") {
  for {
    // Use test clock for setup
    testClock <- ZIO.service[TestClock]
    _ <- testClock.setTime(Duration.zero)
    
    // Use live clock for actual measurement
    result <- Live.live(
      for {
        start <- Clock.currentTime(TimeUnit.MILLISECONDS)
        _ <- ZIO.sleep(100.millis) // Actually sleep
        end <- Clock.currentTime(TimeUnit.MILLISECONDS)
      } yield end - start
    )
  } yield assertTrue(result >= 100L) // Real time elapsed
}

// Mix test and live environments
test("hybrid test environment") {
  Live.withLive(
    // This runs with test environment
    for {
      _ <- Console.printLine("Test output") // Goes to test console
      value <- Random.nextInt(100) // Uses test random
    } yield value
  ) { effect =>
    // This transformation uses live environment
    for {
      _ <- Console.printLine("Live output") // Goes to real console
      result <- effect // Execute inner effect with test env
    } yield result
  }
}

Environment Manipulation

Functions for working with and modifying test environments.

/**
 * Execute with modified annotations
 * @param annotations new annotations service
 * @param zio effect to execute
 * @return effect with modified annotations
 */
def withAnnotations[R, E, A <: Annotations, B](annotations: => A)(
  zio: => ZIO[R, E, B]
): ZIO[R, E, B]

/**
 * Execute with modified test configuration
 * @param testConfig new test configuration
 * @param zio effect to execute  
 * @return effect with modified configuration
 */
def withTestConfig[R, E, A <: TestConfig, B](testConfig: => A)(
  zio: => ZIO[R, E, B]
): ZIO[R, E, B]

/**
 * Execute with modified size configuration
 * @param sized new sized service
 * @param zio effect to execute
 * @return effect with modified size
 */
def withSized[R, E, A <: Sized, B](sized: => A)(
  zio: => ZIO[R, E, B]
): ZIO[R, E, B]

/**
 * Execute with modified live service
 * @param live new live service
 * @param zio effect to execute
 * @return effect with modified live service
 */
def withLive[R, E, A <: Live, B](live: => A)(
  zio: => ZIO[R, E, B]
): ZIO[R, E, B]

Usage Examples:

import zio.test._
import zio._

// Temporarily modify test configuration
test("high sample count test") {
  withTestConfig(TestConfig.live(1, 0, 10000, 5000, 200)) {
    check(Gen.listOf(Gen.anyInt)) { list =>
      assertTrue(list.reverse.reverse == list)
    }
  }
}

// Temporarily modify generator size
test("large data structures") {
  withSized(Sized(1000)) {
    check(Gen.listOf(Gen.anyString)) { largeList =>
      assertTrue(largeList.size <= 1000)
    }
  }
}

// Environment scoped modifications
test("scoped environment changes") {
  for {
    // Normal size
    normalList <- Gen.listOf(Gen.anyInt).sample.runHead
    _ <- ZIO.scoped {
      withSizedScoped(Sized(10)) {
        // Large size in this scope
        Gen.listOf(Gen.anyInt).sample.runHead.map { largeList =>
          // Compare sizes
          assertTrue(largeList.exists(_.size > normalList.map(_.size).getOrElse(0)))
        }
      }
    }
  } yield assertTrue(true)
}