Composable test transformations for modifying test behavior including retry, timeout, and parallel execution.
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]
}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]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): TestAspectPolyAspects 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: TestAspectPolyAspects 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]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: TestAspectPolyAspects 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]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]/**
* 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]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.parallelsuite("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)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.flakysuite("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))
}
)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)
)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)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 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// 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)