Test runners and executors for running test suites with configurable behavior and reporting.
Main component for executing test specifications.
/**
* Test runner that executes test specifications
* @tparam R - Environment requirements
* @tparam E - Error type
*/
case class TestRunner[+R, -E](executor: TestExecutor[R, E]) {
/** Executes a test specification */
def run[R1 <: R, E1 >: E](spec: ZSpec[R1, E1]): URIO[R1, ExecutedSpec[E1]]
/** Creates new runner with different executor */
def withExecutor[R1, E1](executor: TestExecutor[R1, E1]): TestRunner[R1, E1]
}
object TestRunner {
/** Default test runner with standard test environment */
def default[R](testEnvironment: ZLayer[Any, Nothing, R]): TestRunner[R, Any]
}Core execution engine for running tests with reporting.
/**
* Executes test specifications with configurable behavior
* @tparam R - Environment requirements
* @tparam E - Error type
*/
abstract class TestExecutor[-R, -E] {
/** Executes specification with reporter */
def run[R1 <: R, E1 >: E](
spec: ZSpec[R1, E1],
reporter: TestReporter[E1]
): URIO[R1, ExecutedSpec[E1]]
/** Executes specification with environment layer */
def runSpec[R1 <: R, E1 >: E](
spec: ZSpec[R1, E1],
environmentLayer: ZLayer[Any, Nothing, R1]
): UIO[ExecutedSpec[E1]]
}
object TestExecutor {
/** Default executor with standard configuration */
def default[R](testEnvironment: ZLayer[Any, Nothing, R]): TestExecutor[R, Any]
}Abstract base classes for creating executable test specifications.
/**
* Base class for runnable test specifications
* @tparam R - Environment requirements
* @tparam E - Error type
*/
abstract class RunnableSpec[-R, +E] {
/** The test specification to run */
def spec: ZSpec[R, E]
/** Test runner configuration */
def runner: TestRunner[R, E]
/** Test aspects to apply to all tests */
def aspects: List[TestAspect[Nothing, R, Nothing, Any]]
/** Platform-specific configuration */
def platform: TestPlatform
/** Executes the specification */
def runSpec(spec: ZSpec[R, E]): URIO[R, ExecutedSpec[E]]
}
/**
* Default runnable spec with test environment
*/
abstract class DefaultRunnableSpec extends RunnableSpec[TestEnvironment, Any] {
/** Test specification with standard test environment */
def spec: ZSpec[TestEnvironment, Any]
/** Default aspects including timeout warning */
override def aspects: List[TestAspect[Nothing, TestEnvironment, Nothing, Any]] =
List(TestAspect.timeoutWarning(60.seconds))
/** Default test runner */
override def runner: TestRunner[TestEnvironment, Any] = defaultTestRunner
}
/**
* Mutable runnable spec for dynamic test creation
*/
abstract class MutableRunnableSpec extends RunnableSpec[TestEnvironment, Any] {
/** Mutable test specification */
def spec: ZSpec[TestEnvironment, Any]
}Result of test execution with hierarchical structure.
/**
* Result of executing a test specification
* @tparam E - Error type
*/
case class ExecutedSpec[+E](
caseValue: SpecCase[Any, TestFailure[E], TestSuccess, ExecutedSpec[E]]
) {
/** Folds over the execution tree */
def fold[Z](f: ExecutedSpec[E] => Z): Z
/** Checks if any spec matches predicate */
def exists(f: ExecutedSpec[E] => Boolean): Boolean
/** Gets all failed specs */
def failures: List[ExecutedSpec[E]]
/** Gets all successful specs */
def successes: List[ExecutedSpec[E]]
/** Gets all ignored specs */
def ignored: List[ExecutedSpec[E]]
/** Counts total number of tests */
def size: Int
/** Transforms the execution result */
def transform[E1](f: SpecCase[Any, TestFailure[E], TestSuccess, ExecutedSpec[E]] =>
SpecCase[Any, TestFailure[E1], TestSuccess, ExecutedSpec[E1]]): ExecutedSpec[E1]
}System for reporting test results with customizable output.
/**
* Reporter for test execution results
* @tparam E - Error type
*/
type TestReporter[-E] = (Duration, ExecutedSpec[E]) => URIO[TestLogger, Unit]
object TestReporter {
/** Silent reporter that produces no output */
val silent: TestReporter[Any]
}
/**
* Default test reporter with console output
*/
object DefaultTestReporter {
/** Standard console reporter */
def apply[E](duration: Duration, executedSpec: ExecutedSpec[E]): URIO[TestLogger, Unit]
}Summary information about test execution.
/**
* Summary of test execution results
*/
case class Summary(
success: Int, // Number of successful tests
fail: Int, // Number of failed tests
ignore: Int, // Number of ignored tests
summary: String // Textual summary
) {
/** Total number of tests */
def total: Int = success + fail + ignore
/** Whether all tests passed */
def isSuccess: Boolean = fail == 0
}
object Summary {
/** Creates summary from executed spec */
def fromExecutedSpec[E](executedSpec: ExecutedSpec[E]): Summary
}
/**
* Builder for constructing test summaries
*/
class SummaryBuilder {
/** Adds successful test */
def addSuccess(): SummaryBuilder
/** Adds failed test */
def addFailure(): SummaryBuilder
/** Adds ignored test */
def addIgnored(): SummaryBuilder
/** Builds final summary */
def build(): Summary
}Platform-specific test execution configuration.
/**
* Platform-specific test configuration
*/
trait TestPlatform {
/** Whether platform is JVM */
def isJVM: Boolean
/** Whether platform is JavaScript */
def isJS: Boolean
/** Whether platform is Native */
def isNative: Boolean
/** Platform-specific test execution timeout */
def timeout: Duration
}
object TestPlatform {
/** Default platform configuration */
val default: TestPlatform
/** Check if running on JVM */
def isJVM: Boolean
/** Check if running on JavaScript */
def isJS: Boolean
/** Check if running on Native */
def isNative: Boolean
}import zio.test._
import zio.test.environment.TestEnvironment
object BasicSpec extends DefaultRunnableSpec {
def spec = suite("Basic Tests")(
test("simple test") {
assert(2 + 2)(equalTo(4))
},
testM("effectful test") {
assertM(ZIO.succeed(42))(equalTo(42))
}
)
}import zio.test._
import zio.duration._
object CustomRunnerSpec extends RunnableSpec[TestEnvironment, Any] {
def spec = suite("Custom Runner Tests")(
// test definitions...
)
override def aspects = List(
TestAspect.timeout(30.seconds),
TestAspect.parallel,
TestAspect.retryN(3)
)
override def runner = TestRunner(
TestExecutor.default(testEnvironment)
)
}object DynamicSpec extends MutableRunnableSpec {
def spec = {
val dynamicTests = (1 to 5).map { i =>
test(s"dynamic test $i") {
assert(i * 2)(isGreaterThan(i))
}
}
suite("Dynamic Tests")(dynamicTests: _*)
}
}import zio.test._
val customReporter: TestReporter[Any] = (duration, executedSpec) => {
for {
summary <- ZIO.succeed(Summary.fromExecutedSpec(executedSpec))
_ <- TestLogger.logLine(s"Tests completed in ${duration.toMillis}ms")
_ <- TestLogger.logLine(s"Passed: ${summary.success}")
_ <- TestLogger.logLine(s"Failed: ${summary.fail}")
_ <- TestLogger.logLine(s"Ignored: ${summary.ignore}")
} yield ()
}
object CustomReporterSpec extends DefaultRunnableSpec {
def spec = suite("Custom Reporter Tests")(
// test definitions...
)
override def runner = TestRunner(
TestExecutor.default(testEnvironment)
).withReporter(customReporter)
}object PlatformSpec extends DefaultRunnableSpec {
def spec = suite("Platform Tests")(
test("JVM only test") {
assertTrue(TestPlatform.isJVM)
} @@ TestAspect.jvmOnly,
test("JS only test") {
assertTrue(TestPlatform.isJS)
} @@ TestAspect.jsOnly,
suite("Cross-platform tests")(
test("works everywhere") {
assert(2 + 2)(equalTo(4))
}
)
)
}import zio.test._
val mySpec = suite("Programmatic Tests")(
test("test 1")(assert(true)(isTrue)),
test("test 2")(assert(false)(isFalse))
)
val program = for {
runner <- ZIO.succeed(defaultTestRunner)
result <- runner.run(mySpec)
summary = Summary.fromExecutedSpec(result)
_ <- console.putStrLn(s"Tests: ${summary.total}, Passed: ${summary.success}")
} yield summary
// Run with test environment
program.provideLayer(testEnvironment ++ Console.live)object TimeoutSpec extends DefaultRunnableSpec {
def spec = suite("Timeout Tests")(
testM("fast test") {
assertM(ZIO.succeed(42))(equalTo(42))
},
testM("slow test") {
for {
_ <- ZIO.sleep(10.seconds)
result <- ZIO.succeed(42)
} yield assert(result)(equalTo(42))
} @@ TestAspect.timeout(5.seconds)
)
override def aspects = List(
TestAspect.timeoutWarning(2.seconds)
)
}