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

test-aspects.mddocs/

Test Aspects

Cross-cutting concerns for test execution including timeouts, retries, parallelism, and platform-specific behavior.

Capabilities

TestAspect Trait

Core trait for aspects that modify test execution behavior.

/**
 * A test aspect that can transform test specifications
 * @tparam LowerR minimum environment requirement
 * @tparam UpperR maximum environment requirement  
 * @tparam LowerE minimum error type
 * @tparam UpperE maximum error type
 */
trait TestAspect[-LowerR, +UpperR, -LowerE, +UpperE] {
  /**
   * Apply this aspect to a test specification
   * @param spec the test specification to transform
   * @return transformed specification
   */
  def apply[R >: LowerR <: UpperR, E >: LowerE <: UpperE](
    spec: Spec[R, E]
  ): Spec[R, E]
  
  /**
   * Compose with another test aspect
   * @param that other aspect to compose with
   * @return composed aspect
   */
  def @@[LowerR1 <: LowerR, UpperR1 >: UpperR, LowerE1 <: LowerE, UpperE1 >: UpperE](
    that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
  ): TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
}

// Type aliases for common patterns
type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]
type TestAspectAtLeastR[-R] = TestAspect[Nothing, R, Nothing, Any]

Timing and Execution Control

Aspects for controlling test timing, timeouts, and execution behavior.

/**
 * Apply timeout to test execution
 * @param duration maximum duration before timeout
 * @return aspect that times out tests exceeding duration
 */
def timeout(duration: Duration): TestAspectAtLeastR[Live]

/**
 * Retry failing tests with exponential backoff
 * @param n maximum number of retry attempts
 * @return aspect that retries failed tests
 */
def retry(n: Int): TestAspectPoly

/**
 * Retry tests until they succeed or max attempts reached
 * Uses exponential backoff and jittering for better behavior
 */
val eventually: TestAspectAtLeastR[Live]

/**
 * Mark tests as flaky (expected to fail intermittently)
 * Flaky tests that fail are reported differently
 */
val flaky: TestAspectPoly

/**
 * Mark test as non-flaky (opposite of flaky)
 * Ensures test failures will fail the build
 */
val nonFlaky: TestAspectPoly

/**
 * Mark tests to be ignored (not executed)
 * Ignored tests are skipped but reported in results
 */
val ignore: TestAspectPoly

/**
 * Repeat test execution N times
 * @param n number of times to repeat the test
 */
def repeats(n: Int): TestAspectPoly

/**
 * Execute tests with live clock instead of test clock
 * Useful for tests that need real time progression
 */
val withLiveClock: TestAspectAtLeastR[Live]

/**
 * Execute tests with live console instead of test console
 * Useful for tests that need real console I/O
 */
val withLiveConsole: TestAspectAtLeastR[Live]

/**
 * Execute tests with live random instead of test random
 * Useful for tests that need non-deterministic randomness
 */
val withLiveRandom: TestAspectAtLeastR[Live]

/**
 * Execute tests with live system instead of test system
 * Useful for tests that need real system environment
 */
val withLiveSystem: TestAspectAtLeastR[Live]

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Timeout aspects
test("fast operation") {
  ZIO.sleep(100.millis) *> assertTrue(true)
} @@ timeout(1.second)

suite("API Tests")(
  test("get users") { /* test logic */ },
  test("create user") { /* test logic */ }
) @@ timeout(30.seconds)

// Retry and flaky tests
test("external service call") {
  // Might fail due to network issues
  externalService.getData.map(data => assertTrue(data.nonEmpty))
} @@ eventually

test("timing sensitive operation") {
  // Occasionally fails due to timing
  timingSensitiveOperation
} @@ flaky @@ retry(3)

// Live service aspects
test("actual time measurement") {
  for {
    start <- Clock.currentTime(TimeUnit.MILLISECONDS)
    _ <- ZIO.sleep(100.millis)
    end <- Clock.currentTime(TimeUnit.MILLISECONDS)
  } yield assertTrue(end - start >= 100)
} @@ withLiveClock

Parallelism and Execution Strategy

Aspects controlling how tests are executed relative to each other.

/**
 * Execute child tests in parallel
 * Tests within the same suite run concurrently
 */
val parallel: TestAspectPoly

/**
 * Execute child tests sequentially (default behavior)
 * Tests run one after another in order
 */
val sequential: TestAspectPoly

/**
 * Control parallelism level for test execution
 * @param n maximum number of concurrent tests
 */
def parallelN(n: Int): TestAspectPoly

/**
 * Execute each test in an isolated fiber
 * Provides better isolation but more overhead
 */
val fibers: TestAspectPoly

/**
 * Execute tests on a specific executor
 * @param executor custom executor for test execution
 */
def executor(executor: Executor): TestAspectPoly

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Parallel execution
suite("Independent Tests")(
  test("test A") { /* independent test */ },
  test("test B") { /* independent test */ },
  test("test C") { /* independent test */ }
) @@ parallel

// Sequential execution (explicit)
suite("Dependent Tests")(
  test("setup") { /* setup state */ },
  test("use setup") { /* depends on setup */ },
  test("cleanup") { /* cleanup state */ }
) @@ sequential

// Limited parallelism
suite("Database Tests")(
  test("query 1") { /* database query */ },
  test("query 2") { /* database query */ },
  test("query 3") { /* database query */ }
) @@ parallelN(2) // At most 2 concurrent queries

// Custom executor
suite("CPU Intensive Tests")(
  test("heavy computation") { /* CPU bound test */ }
) @@ executor(Runtime.defaultExecutor)

Platform and Environment Filtering

Aspects for controlling test execution based on platform or environment conditions.

/**
 * Execute only on JVM platform
 */
val jvm: TestAspectPoly

/**
 * Execute only on Scala.js platform
 */
val js: TestAspectPoly

/**
 * Execute only on Scala Native platform
 */
val native: TestAspectPoly

/**
 * Execute only on Unix/Linux platforms
 */
val unix: TestAspectPoly

/**
 * Execute only on Windows platforms
 */
val windows: TestAspectPoly

/**
 * Execute based on system property condition
 * @param property system property name
 * @param value expected property value
 */
def ifProp(property: String, value: String): TestAspectPoly

/**
 * Execute based on environment variable condition
 * @param variable environment variable name
 * @param value expected variable value
 */
def ifEnv(variable: String, value: String): TestAspectPoly

/**
 * Execute based on custom condition
 * @param condition predicate determining execution
 */
def conditional(condition: Boolean): TestAspectPoly

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Platform-specific tests
suite("File System Tests")(
  test("Unix file permissions") {
    /* Unix-specific file operations */
  } @@ unix,
  
  test("Windows file paths") {
    /* Windows-specific path handling */
  } @@ windows,
  
  test("Cross-platform file I/O") {
    /* Works on all platforms */
  }
)

// JavaScript-specific tests
suite("Browser Tests")(
  test("DOM manipulation") {
    /* Browser-specific functionality */
  } @@ js,
  
  test("Node.js file system") {
    /* Node.js specific APIs */
  } @@ js
)

// Conditional execution
suite("Integration Tests")(
  test("database integration") {
    /* Requires database */
  } @@ ifEnv("DATABASE_URL", "test"),
  
  test("debug mode features") {
    /* Only in debug builds */
  } @@ ifProp("debug", "true")
)

Test Repetition and Sampling

Aspects for controlling how many times tests are executed.

/**
 * Repeat test execution multiple times
 * @param n number of repetitions
 */
def repeats(n: Int): TestAspectPoly

/**
 * Configure number of samples for property-based tests
 * @param n number of samples to generate and test
 */
def samples(n: Int): TestAspectPoly

/**
 * Configure shrinking attempts for failed property tests
 * @param n maximum shrinking attempts
 */
def shrinks(n: Int): TestAspectPoly

/**
 * Set size parameter for generators
 * @param n size value for Sized environment
 */
def size(n: Int): TestAspectPoly

/**
 * Provide custom test configuration
 * @param config test configuration parameters
 */
def config(config: TestConfig): TestAspectPoly

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Repeat tests for reliability
test("flaky network operation") {
  networkCall.map(result => assertTrue(result.isSuccess))
} @@ repeats(10)

// Property test configuration
test("sorting properties") {
  check(listOf(anyInt)) { list =>
    val sorted = list.sorted
    assertTrue(sorted.size == list.size)
  }
} @@ samples(1000) @@ shrinks(100)

// Size control for generators
test("large data structures") {
  check(listOf(anyString)) { largeList =>
    assertTrue(largeList.forall(_.nonEmpty))
  }
} @@ size(10000)

Output and Logging Control

Aspects for controlling test output and logging behavior.

/**
 * Suppress test output (silent execution)
 */
val silent: TestAspectPoly

/**
 * Enable debug output with detailed information
 */
val debug: TestAspectPoly

/**
 * Add custom annotations to test results
 * @param annotations key-value annotations
 */
def annotate(annotations: TestAnnotation[_]*): TestAspectPoly

/**
 * Tag tests with custom labels for filtering
 * @param tags string tags for test organization
 */
def tag(tags: String*): TestAspectPoly

/**
 * Execute with custom test logger
 * @param logger custom logger implementation
 */
def withLogger(logger: TestLogger): TestAspectPoly

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Silent execution for performance tests
suite("Performance Tests")(
  test("benchmark operation") { /* performance test */ }
) @@ silent

// Tagged tests for organization
suite("API Tests")(
  test("user endpoints") { /* test */ } @@ tag("user", "api"),
  test("admin endpoints") { /* test */ } @@ tag("admin", "api", "security")
)

// Custom annotations
test("database test") {
  /* test logic */
} @@ annotate(
  TestAnnotation.database("postgresql"),
  TestAnnotation.timeout(30.seconds)
)

Aspect Composition

Combining multiple aspects for complex test behavior.

/**
 * Compose aspects using the @@ operator
 * Aspects are applied in order from left to right
 */
val composedAspect: TestAspectPoly = 
  timeout(30.seconds) @@ 
  parallel @@ 
  eventually @@ 
  tag("integration")

/**
 * Conditional aspect application
 * @param condition when to apply the aspect
 * @param aspect aspect to apply conditionally
 */
def when(condition: Boolean)(aspect: TestAspectPoly): TestAspectPoly

Usage Examples:

import zio.test._
import zio.test.TestAspect._

// Complex aspect composition
suite("Complex Integration Suite")(
  test("external service integration") { /* test */ },
  test("database integration") { /* test */ },
  test("file system integration") { /* test */ }
) @@ timeout(60.seconds) @@ 
     parallel @@ 
     eventually @@ 
     tag("integration", "external") @@
     ifEnv("RUN_INTEGRATION_TESTS", "true")

// Conditional aspects
val productionAspects = 
  when(sys.env.contains("PRODUCTION"))(timeout(10.seconds)) @@
  when(sys.env.get("PARALLEL").contains("true"))(parallel) @@
  tag("production")

suite("Production Tests")(
  test("health check") { /* test */ }
) @@ productionAspects

Custom Test Aspects

Creating custom aspects for domain-specific testing needs.

/**
 * Create custom test aspect from transformation function
 * @param transform function that transforms test specs
 * @return custom test aspect
 */
def custom[R, E](
  transform: Spec[R, E] => Spec[R, E]
): TestAspect[Nothing, R, Nothing, E] = 
  new TestAspect[Nothing, R, Nothing, E] {
    def apply[R1 >: Nothing <: R, E1 >: Nothing <: E](
      spec: Spec[R1, E1]
    ): Spec[R1, E1] = transform(spec)
  }

/**
 * Create aspect that modifies test environment
 * @param layer environment transformation layer
 * @return aspect that provides the layer to tests
 */
def provideLayer[R, R1, E](
  layer: ZLayer[R, E, R1]
): TestAspect[Nothing, R, Nothing, Any] = 
  new TestAspect[Nothing, R, Nothing, Any] {
    def apply[R2 >: Nothing <: R, E2 >: Nothing <: Any](
      spec: Spec[R2, E2]
    ): Spec[R2, E2] = spec.provideLayer(layer)
  }

Usage Examples:

import zio.test._

// Custom retry with logging
val retryWithLogging = TestAspect.custom[Any, Any] { spec =>
  spec.mapTest { test =>
    test.tapError(error => 
      Console.printLine(s"Test failed, retrying: $error")
    ).retry(Schedule.recurs(2))
  }
}

// Database transaction aspect
val inTransaction = TestAspect.provideLayer(
  DatabaseTransactionLayer.test
)

// Usage
suite("Database Tests")(
  test("user creation") { /* test */ },
  test("user retrieval") { /* test */ }
) @@ inTransaction @@ retryWithLogging