0
# Test Aspects
1
2
Cross-cutting concerns for test execution including timeouts, retries, parallelism, and platform-specific behavior.
3
4
## Capabilities
5
6
### TestAspect Trait
7
8
Core trait for aspects that modify test execution behavior.
9
10
```scala { .api }
11
/**
12
* A test aspect that can transform test specifications
13
* @tparam LowerR minimum environment requirement
14
* @tparam UpperR maximum environment requirement
15
* @tparam LowerE minimum error type
16
* @tparam UpperE maximum error type
17
*/
18
trait TestAspect[-LowerR, +UpperR, -LowerE, +UpperE] {
19
/**
20
* Apply this aspect to a test specification
21
* @param spec the test specification to transform
22
* @return transformed specification
23
*/
24
def apply[R >: LowerR <: UpperR, E >: LowerE <: UpperE](
25
spec: Spec[R, E]
26
): Spec[R, E]
27
28
/**
29
* Compose with another test aspect
30
* @param that other aspect to compose with
31
* @return composed aspect
32
*/
33
def @@[LowerR1 <: LowerR, UpperR1 >: UpperR, LowerE1 <: LowerE, UpperE1 >: UpperE](
34
that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
35
): TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
36
}
37
38
// Type aliases for common patterns
39
type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]
40
type TestAspectAtLeastR[-R] = TestAspect[Nothing, R, Nothing, Any]
41
```
42
43
### Timing and Execution Control
44
45
Aspects for controlling test timing, timeouts, and execution behavior.
46
47
```scala { .api }
48
/**
49
* Apply timeout to test execution
50
* @param duration maximum duration before timeout
51
* @return aspect that times out tests exceeding duration
52
*/
53
def timeout(duration: Duration): TestAspectAtLeastR[Live]
54
55
/**
56
* Retry failing tests with exponential backoff
57
* @param n maximum number of retry attempts
58
* @return aspect that retries failed tests
59
*/
60
def retry(n: Int): TestAspectPoly
61
62
/**
63
* Retry tests until they succeed or max attempts reached
64
* Uses exponential backoff and jittering for better behavior
65
*/
66
val eventually: TestAspectAtLeastR[Live]
67
68
/**
69
* Mark tests as flaky (expected to fail intermittently)
70
* Flaky tests that fail are reported differently
71
*/
72
val flaky: TestAspectPoly
73
74
/**
75
* Mark test as non-flaky (opposite of flaky)
76
* Ensures test failures will fail the build
77
*/
78
val nonFlaky: TestAspectPoly
79
80
/**
81
* Mark tests to be ignored (not executed)
82
* Ignored tests are skipped but reported in results
83
*/
84
val ignore: TestAspectPoly
85
86
/**
87
* Repeat test execution N times
88
* @param n number of times to repeat the test
89
*/
90
def repeats(n: Int): TestAspectPoly
91
92
/**
93
* Execute tests with live clock instead of test clock
94
* Useful for tests that need real time progression
95
*/
96
val withLiveClock: TestAspectAtLeastR[Live]
97
98
/**
99
* Execute tests with live console instead of test console
100
* Useful for tests that need real console I/O
101
*/
102
val withLiveConsole: TestAspectAtLeastR[Live]
103
104
/**
105
* Execute tests with live random instead of test random
106
* Useful for tests that need non-deterministic randomness
107
*/
108
val withLiveRandom: TestAspectAtLeastR[Live]
109
110
/**
111
* Execute tests with live system instead of test system
112
* Useful for tests that need real system environment
113
*/
114
val withLiveSystem: TestAspectAtLeastR[Live]
115
```
116
117
**Usage Examples:**
118
119
```scala
120
import zio.test._
121
import zio.test.TestAspect._
122
123
// Timeout aspects
124
test("fast operation") {
125
ZIO.sleep(100.millis) *> assertTrue(true)
126
} @@ timeout(1.second)
127
128
suite("API Tests")(
129
test("get users") { /* test logic */ },
130
test("create user") { /* test logic */ }
131
) @@ timeout(30.seconds)
132
133
// Retry and flaky tests
134
test("external service call") {
135
// Might fail due to network issues
136
externalService.getData.map(data => assertTrue(data.nonEmpty))
137
} @@ eventually
138
139
test("timing sensitive operation") {
140
// Occasionally fails due to timing
141
timingSensitiveOperation
142
} @@ flaky @@ retry(3)
143
144
// Live service aspects
145
test("actual time measurement") {
146
for {
147
start <- Clock.currentTime(TimeUnit.MILLISECONDS)
148
_ <- ZIO.sleep(100.millis)
149
end <- Clock.currentTime(TimeUnit.MILLISECONDS)
150
} yield assertTrue(end - start >= 100)
151
} @@ withLiveClock
152
```
153
154
### Parallelism and Execution Strategy
155
156
Aspects controlling how tests are executed relative to each other.
157
158
```scala { .api }
159
/**
160
* Execute child tests in parallel
161
* Tests within the same suite run concurrently
162
*/
163
val parallel: TestAspectPoly
164
165
/**
166
* Execute child tests sequentially (default behavior)
167
* Tests run one after another in order
168
*/
169
val sequential: TestAspectPoly
170
171
/**
172
* Control parallelism level for test execution
173
* @param n maximum number of concurrent tests
174
*/
175
def parallelN(n: Int): TestAspectPoly
176
177
/**
178
* Execute each test in an isolated fiber
179
* Provides better isolation but more overhead
180
*/
181
val fibers: TestAspectPoly
182
183
/**
184
* Execute tests on a specific executor
185
* @param executor custom executor for test execution
186
*/
187
def executor(executor: Executor): TestAspectPoly
188
```
189
190
**Usage Examples:**
191
192
```scala
193
import zio.test._
194
import zio.test.TestAspect._
195
196
// Parallel execution
197
suite("Independent Tests")(
198
test("test A") { /* independent test */ },
199
test("test B") { /* independent test */ },
200
test("test C") { /* independent test */ }
201
) @@ parallel
202
203
// Sequential execution (explicit)
204
suite("Dependent Tests")(
205
test("setup") { /* setup state */ },
206
test("use setup") { /* depends on setup */ },
207
test("cleanup") { /* cleanup state */ }
208
) @@ sequential
209
210
// Limited parallelism
211
suite("Database Tests")(
212
test("query 1") { /* database query */ },
213
test("query 2") { /* database query */ },
214
test("query 3") { /* database query */ }
215
) @@ parallelN(2) // At most 2 concurrent queries
216
217
// Custom executor
218
suite("CPU Intensive Tests")(
219
test("heavy computation") { /* CPU bound test */ }
220
) @@ executor(Runtime.defaultExecutor)
221
```
222
223
### Platform and Environment Filtering
224
225
Aspects for controlling test execution based on platform or environment conditions.
226
227
```scala { .api }
228
/**
229
* Execute only on JVM platform
230
*/
231
val jvm: TestAspectPoly
232
233
/**
234
* Execute only on Scala.js platform
235
*/
236
val js: TestAspectPoly
237
238
/**
239
* Execute only on Scala Native platform
240
*/
241
val native: TestAspectPoly
242
243
/**
244
* Execute only on Unix/Linux platforms
245
*/
246
val unix: TestAspectPoly
247
248
/**
249
* Execute only on Windows platforms
250
*/
251
val windows: TestAspectPoly
252
253
/**
254
* Execute based on system property condition
255
* @param property system property name
256
* @param value expected property value
257
*/
258
def ifProp(property: String, value: String): TestAspectPoly
259
260
/**
261
* Execute based on environment variable condition
262
* @param variable environment variable name
263
* @param value expected variable value
264
*/
265
def ifEnv(variable: String, value: String): TestAspectPoly
266
267
/**
268
* Execute based on custom condition
269
* @param condition predicate determining execution
270
*/
271
def conditional(condition: Boolean): TestAspectPoly
272
```
273
274
**Usage Examples:**
275
276
```scala
277
import zio.test._
278
import zio.test.TestAspect._
279
280
// Platform-specific tests
281
suite("File System Tests")(
282
test("Unix file permissions") {
283
/* Unix-specific file operations */
284
} @@ unix,
285
286
test("Windows file paths") {
287
/* Windows-specific path handling */
288
} @@ windows,
289
290
test("Cross-platform file I/O") {
291
/* Works on all platforms */
292
}
293
)
294
295
// JavaScript-specific tests
296
suite("Browser Tests")(
297
test("DOM manipulation") {
298
/* Browser-specific functionality */
299
} @@ js,
300
301
test("Node.js file system") {
302
/* Node.js specific APIs */
303
} @@ js
304
)
305
306
// Conditional execution
307
suite("Integration Tests")(
308
test("database integration") {
309
/* Requires database */
310
} @@ ifEnv("DATABASE_URL", "test"),
311
312
test("debug mode features") {
313
/* Only in debug builds */
314
} @@ ifProp("debug", "true")
315
)
316
```
317
318
### Test Repetition and Sampling
319
320
Aspects for controlling how many times tests are executed.
321
322
```scala { .api }
323
/**
324
* Repeat test execution multiple times
325
* @param n number of repetitions
326
*/
327
def repeats(n: Int): TestAspectPoly
328
329
/**
330
* Configure number of samples for property-based tests
331
* @param n number of samples to generate and test
332
*/
333
def samples(n: Int): TestAspectPoly
334
335
/**
336
* Configure shrinking attempts for failed property tests
337
* @param n maximum shrinking attempts
338
*/
339
def shrinks(n: Int): TestAspectPoly
340
341
/**
342
* Set size parameter for generators
343
* @param n size value for Sized environment
344
*/
345
def size(n: Int): TestAspectPoly
346
347
/**
348
* Provide custom test configuration
349
* @param config test configuration parameters
350
*/
351
def config(config: TestConfig): TestAspectPoly
352
```
353
354
**Usage Examples:**
355
356
```scala
357
import zio.test._
358
import zio.test.TestAspect._
359
360
// Repeat tests for reliability
361
test("flaky network operation") {
362
networkCall.map(result => assertTrue(result.isSuccess))
363
} @@ repeats(10)
364
365
// Property test configuration
366
test("sorting properties") {
367
check(listOf(anyInt)) { list =>
368
val sorted = list.sorted
369
assertTrue(sorted.size == list.size)
370
}
371
} @@ samples(1000) @@ shrinks(100)
372
373
// Size control for generators
374
test("large data structures") {
375
check(listOf(anyString)) { largeList =>
376
assertTrue(largeList.forall(_.nonEmpty))
377
}
378
} @@ size(10000)
379
```
380
381
### Output and Logging Control
382
383
Aspects for controlling test output and logging behavior.
384
385
```scala { .api }
386
/**
387
* Suppress test output (silent execution)
388
*/
389
val silent: TestAspectPoly
390
391
/**
392
* Enable debug output with detailed information
393
*/
394
val debug: TestAspectPoly
395
396
/**
397
* Add custom annotations to test results
398
* @param annotations key-value annotations
399
*/
400
def annotate(annotations: TestAnnotation[_]*): TestAspectPoly
401
402
/**
403
* Tag tests with custom labels for filtering
404
* @param tags string tags for test organization
405
*/
406
def tag(tags: String*): TestAspectPoly
407
408
/**
409
* Execute with custom test logger
410
* @param logger custom logger implementation
411
*/
412
def withLogger(logger: TestLogger): TestAspectPoly
413
```
414
415
**Usage Examples:**
416
417
```scala
418
import zio.test._
419
import zio.test.TestAspect._
420
421
// Silent execution for performance tests
422
suite("Performance Tests")(
423
test("benchmark operation") { /* performance test */ }
424
) @@ silent
425
426
// Tagged tests for organization
427
suite("API Tests")(
428
test("user endpoints") { /* test */ } @@ tag("user", "api"),
429
test("admin endpoints") { /* test */ } @@ tag("admin", "api", "security")
430
)
431
432
// Custom annotations
433
test("database test") {
434
/* test logic */
435
} @@ annotate(
436
TestAnnotation.database("postgresql"),
437
TestAnnotation.timeout(30.seconds)
438
)
439
```
440
441
### Aspect Composition
442
443
Combining multiple aspects for complex test behavior.
444
445
```scala { .api }
446
/**
447
* Compose aspects using the @@ operator
448
* Aspects are applied in order from left to right
449
*/
450
val composedAspect: TestAspectPoly =
451
timeout(30.seconds) @@
452
parallel @@
453
eventually @@
454
tag("integration")
455
456
/**
457
* Conditional aspect application
458
* @param condition when to apply the aspect
459
* @param aspect aspect to apply conditionally
460
*/
461
def when(condition: Boolean)(aspect: TestAspectPoly): TestAspectPoly
462
```
463
464
**Usage Examples:**
465
466
```scala
467
import zio.test._
468
import zio.test.TestAspect._
469
470
// Complex aspect composition
471
suite("Complex Integration Suite")(
472
test("external service integration") { /* test */ },
473
test("database integration") { /* test */ },
474
test("file system integration") { /* test */ }
475
) @@ timeout(60.seconds) @@
476
parallel @@
477
eventually @@
478
tag("integration", "external") @@
479
ifEnv("RUN_INTEGRATION_TESTS", "true")
480
481
// Conditional aspects
482
val productionAspects =
483
when(sys.env.contains("PRODUCTION"))(timeout(10.seconds)) @@
484
when(sys.env.get("PARALLEL").contains("true"))(parallel) @@
485
tag("production")
486
487
suite("Production Tests")(
488
test("health check") { /* test */ }
489
) @@ productionAspects
490
```
491
492
## Custom Test Aspects
493
494
Creating custom aspects for domain-specific testing needs.
495
496
```scala { .api }
497
/**
498
* Create custom test aspect from transformation function
499
* @param transform function that transforms test specs
500
* @return custom test aspect
501
*/
502
def custom[R, E](
503
transform: Spec[R, E] => Spec[R, E]
504
): TestAspect[Nothing, R, Nothing, E] =
505
new TestAspect[Nothing, R, Nothing, E] {
506
def apply[R1 >: Nothing <: R, E1 >: Nothing <: E](
507
spec: Spec[R1, E1]
508
): Spec[R1, E1] = transform(spec)
509
}
510
511
/**
512
* Create aspect that modifies test environment
513
* @param layer environment transformation layer
514
* @return aspect that provides the layer to tests
515
*/
516
def provideLayer[R, R1, E](
517
layer: ZLayer[R, E, R1]
518
): TestAspect[Nothing, R, Nothing, Any] =
519
new TestAspect[Nothing, R, Nothing, Any] {
520
def apply[R2 >: Nothing <: R, E2 >: Nothing <: Any](
521
spec: Spec[R2, E2]
522
): Spec[R2, E2] = spec.provideLayer(layer)
523
}
524
```
525
526
**Usage Examples:**
527
528
```scala
529
import zio.test._
530
531
// Custom retry with logging
532
val retryWithLogging = TestAspect.custom[Any, Any] { spec =>
533
spec.mapTest { test =>
534
test.tapError(error =>
535
Console.printLine(s"Test failed, retrying: $error")
536
).retry(Schedule.recurs(2))
537
}
538
}
539
540
// Database transaction aspect
541
val inTransaction = TestAspect.provideLayer(
542
DatabaseTransactionLayer.test
543
)
544
545
// Usage
546
suite("Database Tests")(
547
test("user creation") { /* test */ },
548
test("user retrieval") { /* test */ }
549
) @@ inTransaction @@ retryWithLogging
550
```