CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-zio--zio-test-3

ZIO Test is a featherweight testing library for effectful programs built on ZIO, providing a comprehensive framework for property-based testing, test composition, and resource-safe test execution.

Overview
Eval results
Files

test-aspects.mddocs/

Test Aspects

Cross-cutting concerns for configuring test execution, lifecycle, timeouts, retries, and environment management. Test aspects provide a powerful way to modify test behavior without changing test logic.

Capabilities

Core Test Aspect Type

The fundamental test aspect type for transforming test specifications.

/**
 * Base class for test aspects that transform test specifications
 * @tparam R0 - Input environment requirement  
 * @tparam R1 - Output environment requirement
 * @tparam E0 - Input error type
 * @tparam E1 - Output error type
 */
abstract class TestAspect[-R0, +R1, -E0, +E1] {
  /** Apply this aspect to a test specification */
  def apply[R <: R0, E >: E0](spec: Spec[R, E]): Spec[R1, E1]
  
  /** Compose this aspect with another aspect */
  def >>>[R2 >: R1, R3, E2 >: E1, E3](that: TestAspect[R2, R3, E2, E3]): TestAspect[R0, R3, E0, E3]
  
  /** Compose another aspect with this aspect */
  def <<<[R2, E2](that: TestAspect[R2, R0, E2, E0]): TestAspect[R2, R1, E2, E1]
  
  /** Combine aspects with logical AND */
  def &&[R2 <: R0, R3 >: R1, E2 >: E0, E3 >: E1](that: TestAspect[R2, R3, E2, E3]): TestAspect[R2, R3, E2, E3]
}

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

Basic Test Aspects

Essential aspects for common test scenarios.

/**
 * Identity aspect that does nothing
 */
def identity: TestAspectPoly

/**
 * Ignore all tests (mark as ignored)
 */
def ignore: TestAspectPoly

/**
 * Retry flaky tests up to n times until they pass
 * @param n - Maximum number of retries
 */
def flaky(n: Int): TestAspectPoly

/**
 * Mark tests as non-flaky (disable retries)
 * @param n - Number of times to mark as non-flaky
 */
def nonFlaky(n: Int): TestAspectPoly

/**
 * Repeat tests n times
 * @param n - Number of repetitions
 */
def repeats(n: Int): TestAspectPoly

/**
 * Retry failed tests up to n times
 * @param n - Maximum number of retries
 */
def retries(n: Int): TestAspectPoly

Usage Examples:

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

// Apply aspects using @@ syntax
test("flaky test") {
  // Test that might fail occasionally
  assertTrue(scala.util.Random.nextBoolean())
} @@ flaky(3)

test("repeated test") {
  // Test that should run multiple times
  assertTrue(1 + 1 == 2)  
} @@ repeats(5)

// Ignore specific tests
test("work in progress") {
  assertTrue(false) // This won't run
} @@ ignore

Timeout Aspects

Aspects for controlling test execution time limits.

/**
 * Set timeout for test execution
 * @param duration - Maximum execution time
 */
def timeout(duration: Duration): TestAspectPoly

/**
 * Set timeout with warning before timeout
 * @param duration - Maximum execution time  
 */
def timeoutWarning(duration: Duration): TestAspectPoly

/**
 * Detect non-terminating tests
 * @param duration - Time after which to consider test non-terminating
 */
def nonTermination(duration: Duration): TestAspectPoly

Usage Examples:

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

test("long running operation") {
  ZIO.sleep(30.seconds)
} @@ timeout(1.minute)

test("operation with warning") {
  ZIO.sleep(45.seconds)  
} @@ timeoutWarning(1.minute)

suite("time-sensitive tests")(
  test("test 1") { assertTrue(true) },
  test("test 2") { assertTrue(true) }
) @@ timeout(30.seconds) // Apply to entire suite

Execution Strategy Aspects

Aspects controlling parallel vs sequential execution.

/**
 * Execute tests sequentially
 */
def sequential: TestAspectPoly

/**
 * Execute tests in parallel  
 */
def parallel: TestAspectPoly

/**
 * Execute tests in parallel with specific number of threads
 * @param n - Number of parallel threads
 */
def parallelN(n: Int): TestAspectPoly

/**
 * Use custom execution strategy
 * @param strategy - Execution strategy to use
 */
def executionStrategy(strategy: ExecutionStrategy): TestAspectPoly

Usage Examples:

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

// Run entire suite in parallel
suite("parallel tests")(
  test("test 1") { ZIO.sleep(1.second) *> assertTrue(true) },
  test("test 2") { ZIO.sleep(1.second) *> assertTrue(true) },
  test("test 3") { ZIO.sleep(1.second) *> assertTrue(true) }
) @@ parallel

// Force sequential execution for specific suite
suite("sequential tests")(
  test("setup") { setupTest() },
  test("main") { mainTest() },  
  test("cleanup") { cleanupTest() }
) @@ sequential

// Limit parallelism
suite("limited parallel")(
  // Many tests...
) @@ parallelN(4)

Lifecycle Aspects

Aspects for managing test setup and teardown.

/**
 * Run effect before each test
 * @param zio - Effect to run before each test
 */
def before[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after each test
 * @param zio - Effect to run after each test
 */
def after[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effects before and after each test
 * @param before - Effect to run before each test
 * @param after - Effect to run after each test
 */
def around[R](before: ZIO[R, Nothing, Any], after: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect before all tests in a suite
 * @param zio - Effect to run before all tests
 */
def beforeAll[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after all tests in a suite
 * @param zio - Effect to run after all tests
 */
def afterAll[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effects before and after all tests in a suite
 * @param before - Effect to run before all tests
 * @param after - Effect to run after all tests
 */
def aroundAll[R](before: ZIO[R, Nothing, Any], after: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after successful test
 * @param zio - Effect to run after test success
 */
def afterSuccess[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after failed test
 * @param zio - Effect to run after test failure
 */
def afterFailure[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after all tests succeed
 * @param zio - Effect to run after all tests succeed
 */
def afterAllSuccess[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Run effect after any test fails
 * @param zio - Effect to run after any test fails
 */
def afterAllFailure[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]

Usage Examples:

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

// Database test with setup/teardown
suite("database tests")(
  test("create user") { createUserTest() },
  test("read user") { readUserTest() },
  test("update user") { updateUserTest() },
  test("delete user") { deleteUserTest() }
) @@ beforeAll(initializeDatabase()) @@ afterAll(cleanupDatabase())

// Individual test setup
test("file processing") {
  processFile("test.txt")
} @@ before(createTestFile()) @@ after(deleteTestFile())

// Conditional cleanup
test("resource test") {
  useResource()
} @@ afterSuccess(logSuccess()) @@ afterFailure(logFailure())

Environment Aspects

Aspects for providing dependencies and environment to tests.

/**
 * Provide layer to tests
 * @param layer - ZLayer to provide
 */
def fromLayer[R](layer: ZLayer[Any, Nothing, R]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Provide shared layer to tests (layer is created once and reused)
 * @param layer - ZLayer to provide and share
 */
def fromLayerShared[R](layer: ZLayer[Any, Nothing, R]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Use custom config provider
 * @param provider - ConfigProvider to use
 */
def withConfigProvider(provider: ConfigProvider): TestAspectPoly

Usage Examples:

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

// Provide database layer to tests
suite("service tests")(
  test("user service") {
    for {
      service <- ZIO.service[UserService]
      user    <- service.createUser("Alice")
    } yield assertTrue(user.name == "Alice")
  }
) @@ fromLayer(DatabaseLayer.test ++ UserService.live)

// Shared expensive resource
suite("integration tests")(
  // Multiple tests using shared database connection
) @@ fromLayerShared(DatabaseConnectionPool.live)

// Custom configuration
suite("config tests")(
  test("load config") {
    for {
      config <- ZIO.config[AppConfig]
    } yield assertTrue(config.appName.nonEmpty)
  }
) @@ withConfigProvider(ConfigProvider.fromMap(Map("appName" -> "TestApp")))

Property Testing Configuration

Aspects for configuring property-based test parameters.

/**
 * Set number of samples for property tests
 * @param n - Number of samples to generate
 */
def samples(n: Int): TestAspectPoly

/**
 * Set number of shrinking attempts
 * @param n - Maximum shrinking iterations
 */
def shrinks(n: Int): TestAspectPoly

/**
 * Set generator size parameter
 * @param n - Size parameter for generators
 */
def size(n: Int): TestAspectPoly

/**
 * Set generator size using function
 * @param f - Function to transform size parameter
 */
def sized(f: Int => Int): TestAspectPoly

/**
 * Set random seed for reproducible tests
 * @param seed - Random seed value
 */
def setSeed(seed: Long): TestAspectPoly

Usage Examples:

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

test("property test with more samples") {
  check(Gen.int) { n =>
    assertTrue((n + 1) > n)
  }
} @@ samples(10000)

test("property test with larger values") {
  check(Gen.listOf(Gen.int)) { numbers =>
    assertTrue(numbers.reverse.reverse == numbers)
  }
} @@ size(1000)

test("reproducible property test") {
  check(Gen.int) { n =>
    assertTrue(n.isInstanceOf[Int])
  }
} @@ setSeed(42L)

Platform and Conditional Aspects

Aspects for conditional test execution based on platform or environment.

/**
 * Run only if environment variable matches condition
 * @param name - Environment variable name
 * @param predicate - Condition to check variable value
 */
def ifEnv(name: String, predicate: String => Boolean): TestAspectPoly

/**
 * Run only if environment variable is set
 * @param name - Environment variable name
 */
def ifEnvSet(name: String): TestAspectPoly

/**
 * Run only if environment variable is not set
 * @param name - Environment variable name
 */
def ifEnvNotSet(name: String): TestAspectPoly

/**
 * Run conditionally based on environment variable option
 * @param name - Environment variable name
 * @param predicate - Condition to check optional value
 */
def ifEnvOption(name: String, predicate: Option[String] => Boolean): TestAspectPoly

/**
 * Run only if system property matches condition
 * @param name - System property name
 * @param predicate - Condition to check property value
 */
def ifProp(name: String, predicate: String => Boolean): TestAspectPoly

/**
 * Run only if system property is set
 * @param name - System property name
 */
def ifPropSet(name: String): TestAspectPoly

/**
 * Run only if system property is not set
 * @param name - System property name
 */
def ifPropNotSet(name: String): TestAspectPoly

/**
 * Run conditionally based on system property option
 * @param name - System property name
 * @param predicate - Condition to check optional value
 */
def ifPropOption(name: String, predicate: Option[String] => Boolean): TestAspectPoly

/**
 * Run only on JVM platform
 */
def jvmOnly: TestAspectPoly

/**
 * Run only on JavaScript platform
 */
def jsOnly: TestAspectPoly

/**
 * Run only on Scala Native platform
 */
def nativeOnly: TestAspectPoly

/**
 * Run only on Unix systems
 */
def unix: TestAspectPoly

/**
 * Run only on Windows systems
 */
def windows: TestAspectPoly

/**
 * Run conditionally based on operating system
 * @param predicate - Condition to check OS name
 */
def os(predicate: String => Boolean): TestAspectPoly

/**
 * Run only on Scala 2
 */
def scala2Only: TestAspectPoly

/**
 * Run only on Scala 3
 */
def scala3Only: TestAspectPoly

Usage Examples:

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

// Platform-specific tests
test("JVM-specific functionality") {
  // JVM-only code
  assertTrue(System.getProperty("java.version").nonEmpty)
} @@ jvmOnly

test("Unix file permissions") {
  // Unix-specific test
  assertTrue(java.io.File.separatorChar == '/')
} @@ unix

// Environment-conditional tests
test("production database test") {
  // Only run if ENVIRONMENT=production
  connectToDatabase()
} @@ ifEnv("ENVIRONMENT", _ == "production")

test("CI-only test") {
  // Only run in CI environment
  runExpensiveTest()
} @@ ifEnvSet("CI")

// Scala version specific
test("Scala 3 feature") {
  // Test Scala 3 specific features
  assertTrue(true)
} @@ scala3Only

State Management Aspects

Aspects for saving and restoring test service state.

/**
 * Restore using custom restorable
 * @param restorable - Custom restorable implementation
 */
def restore[R](restorable: Restorable[R]): TestAspect[Nothing, R, Nothing, Any]

/**
 * Restore test clock state after each test
 */
def restoreTestClock: TestAspectPoly

/**
 * Restore test console state after each test
 */
def restoreTestConsole: TestAspectPoly

/**
 * Restore test random state after each test
 */
def restoreTestRandom: TestAspectPoly

/**
 * Restore test system state after each test
 */
def restoreTestSystem: TestAspectPoly

/**
 * Restore entire test environment state after each test
 */
def restoreTestEnvironment: TestAspectPoly

Usage Examples:

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

suite("clock tests")(
  test("advance time") {
    TestClock.adjust(1.hour) *> 
    assertTrue(true)
  },
  test("time is reset") {
    // Clock state restored from previous test
    for {
      time <- Clock.instant
    } yield assertTrue(time.getEpochSecond == 0)
  }
) @@ restoreTestClock

suite("console tests")(
  test("write output") {
    Console.printLine("test output") *>
    assertTrue(true)
  },
  test("output is cleared") {
    // Console state restored from previous test
    for {
      output <- TestConsole.output
    } yield assertTrue(output.isEmpty)
  }
) @@ restoreTestConsole

Diagnostic and Analysis Aspects

Aspects for debugging and analyzing test execution.

/**
 * Add diagnostic information to test execution
 * @param duration - Duration to wait before showing diagnostics
 */
def diagnose(duration: Duration): TestAspectPoly

/**
 * Suppress test output (run silently)
 */
def silent: TestAspectPoly

/**
 * Add tags to tests for filtering and organization
 * @param tag - Primary tag
 * @param tags - Additional tags
 */
def tag(tag: String, tags: String*): TestAspectPoly

Verification Aspects

Aspects for adding verification conditions to tests.

/**
 * Add verification condition to test results
 * @param assertion - Assertion to verify against test result
 */
def verify(assertion: Assertion[TestResult]): TestAspectPoly

/**
 * Expect test to fail with specific condition
 * @param assertion - Assertion to verify against test failure
 */
def failing[E](assertion: Assertion[TestFailure[E]]): TestAspect[Nothing, Any, E, Nothing]

Usage Examples:

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

test("expected failure") {
  // This test is expected to fail
  assertTrue(false)
} @@ failing(anything)

test("performance verification") {
  expensiveOperation()
} @@ verify(anything) @@ diagnose(5.seconds)

// Tagged tests for organization
suite("integration tests")(
  test("database test") { dbTest() } @@ tag("db", "integration"),
  test("api test") { apiTest() } @@ tag("api", "integration"),
  test("auth test") { authTest() } @@ tag("auth", "integration")
)

Aspect Composition

Methods for combining and composing aspects.

// Combine aspects using && operator
val combinedAspect = timeout(30.seconds) && parallel && samples(1000)

// Chain aspects using >>> operator  
val chainedAspect = beforeAll(setup()) >>> timeout(1.minute) >>> afterAll(cleanup())

// Apply multiple aspects to test
test("complex test") {
  complexOperation()
} @@ timeout(1.minute) @@ flaky(3) @@ restoreTestClock @@ tag("slow", "integration")

Usage Examples:

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

// Complex aspect composition
val integrationTestAspect = 
  beforeAll(setupIntegrationEnvironment()) @@
  afterAll(cleanupIntegrationEnvironment()) @@
  timeout(5.minutes) @@
  flaky(2) @@
  tag("integration") @@
  ifEnvSet("RUN_INTEGRATION_TESTS")

suite("integration tests")(
  test("end-to-end workflow") { e2eTest() },
  test("external service integration") { externalServiceTest() }
) @@ integrationTestAspect

Install with Tessl CLI

npx tessl i tessl/maven-dev-zio--zio-test-3

docs

assertions.md

core-testing.md

index.md

property-testing.md

smart-assertions.md

test-aspects.md

test-services.md

tile.json