0
# Test Specifications
1
2
Base classes and utilities for creating runnable test specifications. The specification framework provides the structure for organizing and executing tests.
3
4
## Capabilities
5
6
### Core Specification Classes
7
8
Foundation classes for creating test specifications.
9
10
```scala { .api }
11
/**
12
* Base spec class representing a test specification tree
13
* @param R - Environment required by the spec
14
* @param E - Error type the spec may produce
15
* @param T - Type of test results
16
*/
17
final case class Spec[-R, +E, +T](caseValue: SpecCase[R, E, T, Spec[R, E, T]]) {
18
/**
19
* Combine this spec with another spec
20
* @param that - Spec to combine with
21
* @returns Combined spec containing both
22
*/
23
def +[R1 <: R, E1 >: E, T1 >: T](that: Spec[R1, E1, T1]): Spec[R1, E1, T1]
24
25
/**
26
* Apply a test aspect to this spec
27
* @param aspect - Aspect to apply
28
* @returns Spec with aspect applied
29
*/
30
def @@[R0 <: R1, R1 <: R, E0, E1, E2 >: E0 <: E1](
31
aspect: TestAspect[R0, R1, E0, E1]
32
): ZSpec[R1, E2]
33
34
/**
35
* Conditionally execute this spec
36
* @param b - Condition for execution
37
* @returns Spec that runs only if condition is true
38
*/
39
def when(b: Boolean): Spec[R, E, T]
40
41
/**
42
* Provide a layer to satisfy environment requirements
43
* @param layer - Layer providing required services
44
* @returns Spec with environment satisfied
45
*/
46
def provideSomeLayer[R0](layer: ZLayer[R0, Nothing, R]): Spec[R0, E, T]
47
48
/**
49
* Provide complete layer for environment
50
* @param layer - Layer providing all required services
51
* @returns Spec with complete environment
52
*/
53
def provideLayer[E1 >: E](layer: ZLayer[Any, E1, R]): Spec[Any, E1, T]
54
55
/**
56
* Map over the test results
57
* @param f - Function to transform test results
58
* @returns Spec with transformed results
59
*/
60
def map[T1](f: T => T1): Spec[R, E, T1]
61
62
/**
63
* Transform spec with access to environment and error information
64
* @param f - Transformation function
65
* @returns Transformed spec
66
*/
67
def transform[R1, E1, T1](f: Spec[R, E, T] => Spec[R1, E1, T1]): Spec[R1, E1, T1]
68
}
69
70
/**
71
* Companion object with spec creation utilities
72
*/
73
object Spec {
74
/**
75
* Create a labeled spec
76
* @param label - Description/name for the spec
77
* @param spec - Spec to label
78
* @returns Labeled spec
79
*/
80
def labeled[R, E, T](label: String, spec: Spec[R, E, T]): Spec[R, E, T]
81
82
/**
83
* Create a spec containing multiple child specs
84
* @param specs - Child specs to include
85
* @returns Combined spec
86
*/
87
def multiple[R, E, T](specs: Chunk[Spec[R, E, T]]): Spec[R, E, T]
88
89
/**
90
* Create a single test spec
91
* @param test - Test to include
92
* @param annotations - Test annotations
93
* @returns Spec containing the test
94
*/
95
def test[R, E, T](test: ZIO[R, E, T], annotations: TestAnnotationMap): Spec[R, E, T]
96
97
/**
98
* Create an empty spec
99
* @returns Empty spec that succeeds immediately
100
*/
101
def empty[R, E, T]: Spec[R, E, T]
102
}
103
```
104
105
### Runnable Specification Classes
106
107
Base classes for creating executable test specifications.
108
109
```scala { .api }
110
/**
111
* Base class for runnable test specifications
112
* @param R - Environment type required by tests
113
* @param E - Error type tests may produce
114
*/
115
abstract class RunnableSpec[R, E] {
116
/**
117
* The test specification to run
118
* @returns Root spec containing all tests
119
*/
120
def spec: ZSpec[R, E]
121
122
/**
123
* Test aspects to apply to all tests
124
* @returns List of aspects to apply
125
*/
126
def aspects: List[TestAspect[Nothing, R, Nothing, Any]] = Nil
127
128
/**
129
* Test runner for executing the specification
130
* @returns Runner configured for this spec
131
*/
132
def runner: TestRunner[R, E]
133
134
/**
135
* Platform-specific test runner selection
136
* @returns Platform-appropriate runner
137
*/
138
def platform: TestPlatform = TestPlatform.default
139
140
/**
141
* Bootstrap layer for setting up test environment
142
* @returns Layer providing test setup
143
*/
144
def bootstrap: ZLayer[Any, Nothing, TestLogger] = TestLogger.fromConsole
145
146
/**
147
* Execute the specification and produce results
148
* @param spec - Specification to run
149
* @returns Effect producing execution results
150
*/
151
private[zio] def runSpec(
152
spec: ZSpec[Environment, Failure]
153
): URIO[TestLogger with Clock, ExecutedSpec[Failure]]
154
}
155
156
/**
157
* Default runnable spec with standard test environment
158
*/
159
abstract class DefaultRunnableSpec extends RunnableSpec[TestEnvironment, Any] {
160
/**
161
* Default aspects applied to all tests
162
* @returns List including timeout warning aspect
163
*/
164
override def aspects: List[TestAspect[Nothing, TestEnvironment, Nothing, Any]] =
165
List(TestAspect.timeoutWarning(60.seconds))
166
167
/**
168
* Default test runner with test environment
169
* @returns Pre-configured runner
170
*/
171
override def runner: TestRunner[TestEnvironment, Any] =
172
defaultTestRunner
173
174
/**
175
* Convenience method for creating suites (delegates to package object)
176
* @param label - Suite name
177
* @param specs - Specs to include in suite
178
* @returns Suite spec
179
*/
180
def suite[R, E, T](label: String)(specs: Spec[R, E, T]*): Spec[R, E, T] =
181
zio.test.suite(label)(specs: _*)
182
183
/**
184
* Convenience method for creating tests (delegates to package object)
185
* @param label - Test name
186
* @param assertion - Test assertion
187
* @returns Test spec
188
*/
189
def test(label: String)(assertion: => TestResult): ZSpec[Any, Nothing] =
190
zio.test.test(label)(assertion)
191
192
/**
193
* Convenience method for creating effectful tests (delegates to package object)
194
* @param label - Test name
195
* @param assertion - Effectful test assertion
196
* @returns Effectful test spec
197
*/
198
def testM[R, E](label: String)(assertion: => ZIO[R, E, TestResult]): ZSpec[R, E] =
199
zio.test.testM(label)(assertion)
200
}
201
202
/**
203
* Mutable runnable spec for imperatively building test suites
204
*/
205
abstract class MutableRunnableSpec extends DefaultRunnableSpec {
206
/**
207
* Mutable collection of test specs
208
*/
209
private var _specs: List[ZSpec[TestEnvironment, Any]] = Nil
210
211
/**
212
* Add a spec to this runnable spec
213
* @param spec - Spec to add
214
*/
215
def +(spec: ZSpec[TestEnvironment, Any]): Unit = {
216
_specs = spec :: _specs
217
}
218
219
/**
220
* Get all added specs as a suite
221
* @returns Combined suite of all added specs
222
*/
223
final def spec: ZSpec[TestEnvironment, Any] =
224
if (_specs.isEmpty) suite("empty")()
225
else suite(getClass.getSimpleName)(_specs.reverse: _*)
226
}
227
228
/**
229
* Default mutable runnable spec
230
*/
231
abstract class DefaultMutableRunnableSpec extends MutableRunnableSpec
232
```
233
234
### Execution Framework
235
236
Classes for running and executing test specifications.
237
238
```scala { .api }
239
/**
240
* Test runner interface for executing specifications
241
* @param R - Environment type
242
* @param E - Error type
243
*/
244
trait TestRunner[+R, +E] {
245
/**
246
* Run a test specification
247
* @param spec - Specification to execute
248
* @returns Effect producing execution results
249
*/
250
def run[R1 <: R, E1 >: E](spec: ZSpec[R1, E1]): URIO[R1 with TestLogger with Clock, ExecutedSpec[E1]]
251
}
252
253
/**
254
* Test executor for managing test execution
255
* @param R - Environment type
256
* @param E - Error type
257
*/
258
trait TestExecutor[+R, +E] {
259
/**
260
* Execute a single test
261
* @param spec - Test specification
262
* @param defExec - Default execution strategy
263
* @returns Effect producing test results
264
*/
265
def run[R1 <: R, E1 >: E](
266
spec: ZSpec[R1, E1],
267
defExec: ExecutionStrategy
268
): ZIO[R1 with TestLogger, Nothing, ExecutedSpec[E1]]
269
}
270
271
/**
272
* Test executor companion with default implementations
273
*/
274
object TestExecutor {
275
/**
276
* Create default test executor
277
* @param environment - Test environment layer
278
* @returns Default executor
279
*/
280
def default[R](environment: ZLayer[Any, Nothing, R]): TestExecutor[R, Any]
281
}
282
```
283
284
### Test Platform Support
285
286
Platform-specific test execution support.
287
288
```scala { .api }
289
/**
290
* Test platform abstraction
291
*/
292
trait TestPlatform {
293
/**
294
* Check if running on JavaScript platform
295
*/
296
def isJS: Boolean
297
298
/**
299
* Check if running on JVM platform
300
*/
301
def isJVM: Boolean
302
303
/**
304
* Check if running on Native platform
305
*/
306
def isNative: Boolean
307
}
308
309
/**
310
* Test platform companion with defaults
311
*/
312
object TestPlatform {
313
/**
314
* Default platform detection
315
*/
316
val default: TestPlatform
317
318
/**
319
* Check if currently on JavaScript platform
320
*/
321
def isJS: Boolean
322
323
/**
324
* Check if currently on JVM platform
325
*/
326
def isJVM: Boolean
327
328
/**
329
* Check if currently on Native platform
330
*/
331
def isNative: Boolean
332
}
333
```
334
335
### Spec Composition and Utilities
336
337
Utilities for building and composing specifications.
338
339
```scala { .api }
340
/**
341
* Spec case representing different types of specifications
342
*/
343
sealed trait SpecCase[+R, +E, +T, +S]
344
345
object SpecCase {
346
/**
347
* Test case containing a single test
348
*/
349
final case class TestCase[+R, +E, +T](test: ZIO[R, E, T], annotations: TestAnnotationMap) extends SpecCase[R, E, T, Nothing]
350
351
/**
352
* Labeled case with description
353
*/
354
final case class LabeledCase[+S](label: String, spec: S) extends SpecCase[Nothing, Nothing, Nothing, S]
355
356
/**
357
* Multiple case containing child specs
358
*/
359
final case class MultipleCase[+S](specs: Chunk[S]) extends SpecCase[Nothing, Nothing, Nothing, S]
360
361
/**
362
* Managed case with resource management
363
*/
364
final case class ManagedCase[+R, +E, +S](managed: ZManaged[R, E, S]) extends SpecCase[R, E, Nothing, S]
365
}
366
```
367
368
**Usage Examples:**
369
370
```scala
371
// Basic runnable spec
372
object MyTests extends DefaultRunnableSpec {
373
def spec = suite("my application tests")(
374
suite("user management")(
375
test("create user") {
376
assert(createUser("john", "john@example.com"))(isSuccess)
377
},
378
testM("find user") {
379
for {
380
user <- createUser("jane", "jane@example.com")
381
found <- findUser(user.id)
382
} yield assert(found)(isSome(equalTo(user)))
383
}
384
),
385
suite("data processing")(
386
test("transform data") {
387
val input = List(1, 2, 3)
388
val expected = List(2, 4, 6)
389
assert(input.map(_ * 2))(equalTo(expected))
390
}
391
)
392
)
393
}
394
395
// Mutable spec for dynamic test construction
396
object DynamicTests extends MutableRunnableSpec {
397
// Add tests dynamically
398
for (i <- 1 to 5) {
399
this + test(s"dynamic test $i") {
400
assert(i * 2)(isGreaterThan(i))
401
}
402
}
403
404
// Add conditional tests
405
if (System.getProperty("run.integration.tests") == "true") {
406
this + suite("integration tests")(
407
testM("external service") {
408
for {
409
result <- callExternalService()
410
} yield assert(result)(isSuccess)
411
}
412
)
413
}
414
}
415
416
// Custom runnable spec with specific environment
417
abstract class DatabaseSpec extends RunnableSpec[Database with TestEnvironment, Any] {
418
override def aspects = List(
419
TestAspect.sequential, // Database tests run sequentially
420
TestAspect.timeout(30.seconds)
421
)
422
423
val databaseLayer: ZLayer[Any, Nothing, Database] = ???
424
425
override def runner = TestRunner(
426
TestExecutor.default(testEnvironment ++ databaseLayer)
427
)
428
429
// Helper methods for database tests
430
def withCleanDatabase[R, E, A](test: ZIO[R, E, A]): ZIO[R with Database, E, A] =
431
Database.cleanAll *> test
432
}
433
```
434
435
## Types
436
437
### Core Specification Types
438
439
```scala { .api }
440
/**
441
* Core spec type
442
*/
443
final case class Spec[-R, +E, +T](caseValue: SpecCase[R, E, T, Spec[R, E, T]])
444
445
/**
446
* ZIO Test specification type (most commonly used)
447
*/
448
type ZSpec[-R, +E] = Spec[R, TestFailure[E], TestSuccess]
449
450
/**
451
* Spec case variants
452
*/
453
sealed trait SpecCase[+R, +E, +T, +S]
454
```
455
456
### Execution Result Types
457
458
```scala { .api }
459
/**
460
* Results of executing a test specification
461
*/
462
final case class ExecutedSpec[+E](
463
caseValue: SpecCase[Any, E, TestSuccess, ExecutedSpec[E]]
464
) {
465
/**
466
* Fold over the execution results
467
*/
468
def fold[Z](
469
test: (TestSuccess, TestAnnotationMap) => Z,
470
labeled: (String, Z) => Z,
471
multiple: Chunk[Z] => Z,
472
managed: Z => Z
473
): Z
474
475
/**
476
* Check if all tests succeeded
477
*/
478
def isSuccess: Boolean
479
480
/**
481
* Get all test failures
482
*/
483
def failures: Chunk[TestFailure[E]]
484
485
/**
486
* Count total number of tests
487
*/
488
def size: Int
489
}
490
491
/**
492
* Summary of test execution results
493
*/
494
final case class Summary(
495
success: Int,
496
fail: Int,
497
ignore: Int,
498
summary: String
499
) {
500
/**
501
* Total number of tests
502
*/
503
def total: Int = success + fail + ignore
504
505
/**
506
* Whether all tests passed
507
*/
508
def isSuccess: Boolean = fail == 0
509
}
510
```
511
512
### Runner and Executor Types
513
514
```scala { .api }
515
/**
516
* Test runner interface
517
*/
518
trait TestRunner[+R, +E]
519
520
/**
521
* Test executor interface
522
*/
523
trait TestExecutor[+R, +E]
524
525
/**
526
* Execution strategy for running tests
527
*/
528
sealed trait ExecutionStrategy
529
object ExecutionStrategy {
530
case object Sequential extends ExecutionStrategy
531
case object Parallel extends ExecutionStrategy
532
case object ParallelN(n: Int) extends ExecutionStrategy
533
}
534
```