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

test-aspects.mddocs/

Test Aspects

Composable test transformations for modifying test behavior including retry, timeout, and parallel execution.

Capabilities

TestAspect Class

Core abstraction for transforming test behavior.

/**
 * Test aspect that transforms test specifications
 * @tparam LowerR - Minimum environment requirement
 * @tparam UpperR - Maximum environment requirement  
 * @tparam LowerE - Minimum error type
 * @tparam UpperE - Maximum error type
 */
abstract class TestAspect[+LowerR, -UpperR, +LowerE, -UpperE] {
  /** Applies aspect to test specification */
  def apply[R, E](spec: ZSpec[R, E]): ZSpec[R, E]
  
  /** Composes with another aspect */
  def andThen[LowerR1, UpperR1, LowerE1, UpperE1](
    that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
  ): TestAspect[LowerR with LowerR1, UpperR1, LowerE with LowerE1, UpperE1]
  
  /** Alias for andThen */  
  def >>>[LowerR1, UpperR1, LowerE1, UpperE1](
    that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
  ): TestAspect[LowerR with LowerR1, UpperR1, LowerE with LowerE1, UpperE1]
}

Lifecycle Aspects

Aspects that run effects before, after, or around test execution.

/** Runs effect before test execution */ 
def before[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Runs effect after test execution */
def after[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Runs effect after test success */
def afterSuccess[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Runs effect after test failure */
def afterFailure[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Runs effects before all tests in suite */
def beforeAll[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Runs effects after all tests in suite */
def afterAll[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

/** Brackets test execution with setup and cleanup */
def around[R, A](
  before: ZIO[R, Nothing, A]
)(after: A => ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]

Execution Control Aspects

Aspects that control how and when tests are executed.

/** Ignores test (marks as skipped) */
val ignore: TestAspectPoly

/** Runs test exactly once */
val once: TestAspectPoly

/** Runs tests in parallel */
val parallel: TestAspectPoly

/** Runs tests sequentially */
val sequential: TestAspectPoly

/** Sets test timeout */
def timeout(duration: Duration): TestAspectPoly

/** Warns about slow tests */
def diagnose(duration: Duration): TestAspectPoly

/** Warns if test takes longer than duration */
def timeoutWarning(duration: Duration): TestAspectPoly

Retry and Flaky Test Aspects

Aspects for handling unreliable or flaky tests.

/** Marks test as flaky (may fail occasionally) */
val flaky: TestAspectPoly

/** Marks test as non-flaky (must always pass) */
val nonFlaky: TestAspectPoly

/** Retries test until success (eventually) */
val eventually: TestAspectPoly

/** Retries test N times on failure */
def retryN(n: Int): TestAspectPoly

/** Retries test according to schedule */
def retry[R](schedule: Schedule[R, TestFailure[Any], Any]): TestAspectAtLeastR[R]

/** Requires test to succeed (no retries) */
val success: TestAspectPoly

Repetition Aspects

Aspects for running tests multiple times.

/** Repeats test N times */
def repeatN(n: Int): TestAspectPoly

/** Repeats test according to schedule */
def repeat[R](schedule: Schedule[R, TestResult, Any]): TestAspectAtLeastR[R]

Configuration Aspects

Aspects that modify test environment or configuration.

/** Sets generator size for property tests */
def sized(size: Int): TestAspectPoly

/** Sets number of shrinking attempts */
def shrinks(n: Int): TestAspectPoly

/** Sets number of samples for property tests */
def samples(n: Int): TestAspectPoly

/** Adds tags to test */
def tag(tag: String, tags: String*): TestAspectPoly

/** Times test execution */
val timed: TestAspectPoly

Platform-Specific Aspects

Aspects that control test execution on different platforms.

/** Runs test only on JVM */
val jvmOnly: TestAspectPoly

/** Runs test only on JavaScript */
val jsOnly: TestAspectPoly

/** Runs test only on Scala Native */
val nativeOnly: TestAspectPoly

/** Runs test only on specified platforms */
def platformSpecific[R, E, A](
  js: => A, 
  jvm: => A
)(f: A => ZTest[R, E]): ZTest[R, E]

Resource Management Aspects

Aspects for managing resources during test execution.

/** Restores test state after execution */
def restore[R](f: ZIO[R, Nothing, TestResult] => ZIO[R, Nothing, TestResult]): TestAspectAtLeastR[R]

/** Provides managed resource to test */
def aroundManaged[R, A](managed: Managed[Nothing, A]): TestAspectAtLeastR[R]

/** Provides layer to test */
def provideLayer[R0, R, E](layer: ZLayer[R0, E, R]): TestAspect[Nothing, R0, E, Any]

/** Provides custom layer to test */
def provideCustomLayer[R0, R, E](layer: ZLayer[R0, E, R]): TestAspect[Nothing, R0, E, Any]

Type Aliases

/**
 * Test aspect that requires at least environment R
 */
type TestAspectAtLeastR[R] = TestAspect[Nothing, R, Nothing, Any]

/**
 * Polymorphic test aspect with no environment or error constraints
 */
type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]

Usage Examples

Basic Aspect Application

import zio.test._
import zio.duration._

// Apply single aspect
test("flaky test") {
  assert(scala.util.Random.nextBoolean())(isTrue)
} @@ TestAspect.flaky

// Apply multiple aspects  
test("complex test") {
  assert(2 + 2)(equalTo(4))
} @@ TestAspect.timeout(30.seconds) @@ TestAspect.retryN(3)

// Apply to entire suite
suite("Parallel Suite")(
  test("test 1")(assert(true)(isTrue)),
  test("test 2")(assert(true)(isTrue))
) @@ TestAspect.parallel

Lifecycle Management

suite("Database Tests")(
  test("insert user") {
    // test implementation
    assert(true)(isTrue)
  },
  
  test("update user") {
    // test implementation  
    assert(true)(isTrue)
  }
) @@ TestAspect.beforeAll(initializeDatabase) @@ 
     TestAspect.afterAll(cleanupDatabase)

// Per-test lifecycle
test("isolated test") {
  assert(true)(isTrue)
} @@ TestAspect.around(createTestData)(cleanupTestData)

Retry and Flaky Tests

import zio.test._
import zio.schedule._

// Simple retry
test("unstable network call") {
  // test that might fail due to network issues
  assert(true)(isTrue)
} @@ TestAspect.retryN(5)

// Eventually succeeds (keeps retrying)
test("eventually passing") {
  assert(scala.util.Random.nextInt(10))(isGreaterThan(8))
} @@ TestAspect.eventually

// Custom retry schedule
test("scheduled retry") {
  assert(true)(isTrue)
} @@ TestAspect.retry(Schedule.exponential(100.millis) && Schedule.recurs(3))

// Mark as flaky but still run
test("known flaky test") {
  assert(System.currentTimeMillis() % 2 == 0)(isTrue)
} @@ TestAspect.flaky

Platform-Specific Tests

suite("Platform Tests")(
  test("JVM specific feature") {
    // test JVM-only functionality
    assert(true)(isTrue)
  } @@ TestAspect.jvmOnly,
  
  test("JavaScript specific feature") {
    // test JS-only functionality
    assert(true)(isTrue)
  } @@ TestAspect.jsOnly,
  
  test("cross-platform feature") {
    assert(2 + 2)(equalTo(4))
  }
)

Performance and Timing

suite("Performance Tests")(
  test("fast operation") {
    // should complete quickly
    assert(true)(isTrue)
  } @@ TestAspect.timeout(1.second),
  
  test("operation with timing") {
    Thread.sleep(100)
    assert(true)(isTrue)
  } @@ TestAspect.timed,
  
  test("slow operation warning") {
    Thread.sleep(50) 
    assert(true)(isTrue)
  } @@ TestAspect.diagnose(10.millis)
)

Property Testing Configuration

import zio.test.Gen._

test("property test configuration") {
  check(int) { n =>
    assert(n * 2)(equalTo(n + n))
  }
} @@ TestAspect.samples(1000) @@ 
     TestAspect.shrinks(100) @@
     TestAspect.sized(50)

Resource Management

val databaseLayer = ZLayer.fromManaged {
  Managed.make(initDatabase)(_.close())
}

suite("Database Integration Tests")(
  test("query users") {
    // test using database
    assert(true)(isTrue)  
  },
  
  test("insert user") {
    // test using database
    assert(true)(isTrue)
  }
) @@ TestAspect.provideCustomLayer(databaseLayer)

Custom Aspects

// Custom aspect that logs test execution
def logged[R <: TestLogger](message: String): TestAspectAtLeastR[R] = 
  TestAspect.before(TestLogger.logLine(s"Starting: $message")) >>>
  TestAspect.after(TestLogger.logLine(s"Finished: $message"))

// Usage  
test("logged test") {
  assert(true)(isTrue)
} @@ logged("my important test")

// Aspect composition
val resilientTest = TestAspect.retryN(3) >>> 
                   TestAspect.timeout(10.seconds) >>>
                   TestAspect.diagnose(1.second)

test("resilient test") {
  assert(true)(isTrue)  
} @@ resilientTest

Conditional Aspects

// Apply aspect conditionally
def whenCI(aspect: TestAspectPoly): TestAspectPoly = 
  if (sys.env.contains("CI")) aspect else TestAspect.identity

test("CI test") {
  assert(true)(isTrue)
} @@ whenCI(TestAspect.retryN(5))

// Environment-based aspects
test("development test") {
  assert(true)(isTrue)  
} @@ TestAspect.ifEnv("ENV", "dev")(TestAspect.ignore)