0
# Test Aspects
1
2
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.
3
4
## Capabilities
5
6
### Core Test Aspect Type
7
8
The fundamental test aspect type for transforming test specifications.
9
10
```scala { .api }
11
/**
12
* Base class for test aspects that transform test specifications
13
* @tparam R0 - Input environment requirement
14
* @tparam R1 - Output environment requirement
15
* @tparam E0 - Input error type
16
* @tparam E1 - Output error type
17
*/
18
abstract class TestAspect[-R0, +R1, -E0, +E1] {
19
/** Apply this aspect to a test specification */
20
def apply[R <: R0, E >: E0](spec: Spec[R, E]): Spec[R1, E1]
21
22
/** Compose this aspect with another aspect */
23
def >>>[R2 >: R1, R3, E2 >: E1, E3](that: TestAspect[R2, R3, E2, E3]): TestAspect[R0, R3, E0, E3]
24
25
/** Compose another aspect with this aspect */
26
def <<<[R2, E2](that: TestAspect[R2, R0, E2, E0]): TestAspect[R2, R1, E2, E1]
27
28
/** Combine aspects with logical AND */
29
def &&[R2 <: R0, R3 >: R1, E2 >: E0, E3 >: E1](that: TestAspect[R2, R3, E2, E3]): TestAspect[R2, R3, E2, E3]
30
}
31
32
/**
33
* Type aliases for common aspect patterns
34
*/
35
type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]
36
type TestAspectAtLeastR[-R] = TestAspect[Nothing, R, Nothing, Any]
37
```
38
39
### Basic Test Aspects
40
41
Essential aspects for common test scenarios.
42
43
```scala { .api }
44
/**
45
* Identity aspect that does nothing
46
*/
47
def identity: TestAspectPoly
48
49
/**
50
* Ignore all tests (mark as ignored)
51
*/
52
def ignore: TestAspectPoly
53
54
/**
55
* Retry flaky tests up to n times until they pass
56
* @param n - Maximum number of retries
57
*/
58
def flaky(n: Int): TestAspectPoly
59
60
/**
61
* Mark tests as non-flaky (disable retries)
62
* @param n - Number of times to mark as non-flaky
63
*/
64
def nonFlaky(n: Int): TestAspectPoly
65
66
/**
67
* Repeat tests n times
68
* @param n - Number of repetitions
69
*/
70
def repeats(n: Int): TestAspectPoly
71
72
/**
73
* Retry failed tests up to n times
74
* @param n - Maximum number of retries
75
*/
76
def retries(n: Int): TestAspectPoly
77
```
78
79
**Usage Examples:**
80
81
```scala
82
import zio.test._
83
import zio.test.TestAspect._
84
85
// Apply aspects using @@ syntax
86
test("flaky test") {
87
// Test that might fail occasionally
88
assertTrue(scala.util.Random.nextBoolean())
89
} @@ flaky(3)
90
91
test("repeated test") {
92
// Test that should run multiple times
93
assertTrue(1 + 1 == 2)
94
} @@ repeats(5)
95
96
// Ignore specific tests
97
test("work in progress") {
98
assertTrue(false) // This won't run
99
} @@ ignore
100
```
101
102
### Timeout Aspects
103
104
Aspects for controlling test execution time limits.
105
106
```scala { .api }
107
/**
108
* Set timeout for test execution
109
* @param duration - Maximum execution time
110
*/
111
def timeout(duration: Duration): TestAspectPoly
112
113
/**
114
* Set timeout with warning before timeout
115
* @param duration - Maximum execution time
116
*/
117
def timeoutWarning(duration: Duration): TestAspectPoly
118
119
/**
120
* Detect non-terminating tests
121
* @param duration - Time after which to consider test non-terminating
122
*/
123
def nonTermination(duration: Duration): TestAspectPoly
124
```
125
126
**Usage Examples:**
127
128
```scala
129
import zio.test._
130
import zio.test.TestAspect._
131
132
test("long running operation") {
133
ZIO.sleep(30.seconds)
134
} @@ timeout(1.minute)
135
136
test("operation with warning") {
137
ZIO.sleep(45.seconds)
138
} @@ timeoutWarning(1.minute)
139
140
suite("time-sensitive tests")(
141
test("test 1") { assertTrue(true) },
142
test("test 2") { assertTrue(true) }
143
) @@ timeout(30.seconds) // Apply to entire suite
144
```
145
146
### Execution Strategy Aspects
147
148
Aspects controlling parallel vs sequential execution.
149
150
```scala { .api }
151
/**
152
* Execute tests sequentially
153
*/
154
def sequential: TestAspectPoly
155
156
/**
157
* Execute tests in parallel
158
*/
159
def parallel: TestAspectPoly
160
161
/**
162
* Execute tests in parallel with specific number of threads
163
* @param n - Number of parallel threads
164
*/
165
def parallelN(n: Int): TestAspectPoly
166
167
/**
168
* Use custom execution strategy
169
* @param strategy - Execution strategy to use
170
*/
171
def executionStrategy(strategy: ExecutionStrategy): TestAspectPoly
172
```
173
174
**Usage Examples:**
175
176
```scala
177
import zio.test._
178
import zio.test.TestAspect._
179
180
// Run entire suite in parallel
181
suite("parallel tests")(
182
test("test 1") { ZIO.sleep(1.second) *> assertTrue(true) },
183
test("test 2") { ZIO.sleep(1.second) *> assertTrue(true) },
184
test("test 3") { ZIO.sleep(1.second) *> assertTrue(true) }
185
) @@ parallel
186
187
// Force sequential execution for specific suite
188
suite("sequential tests")(
189
test("setup") { setupTest() },
190
test("main") { mainTest() },
191
test("cleanup") { cleanupTest() }
192
) @@ sequential
193
194
// Limit parallelism
195
suite("limited parallel")(
196
// Many tests...
197
) @@ parallelN(4)
198
```
199
200
### Lifecycle Aspects
201
202
Aspects for managing test setup and teardown.
203
204
```scala { .api }
205
/**
206
* Run effect before each test
207
* @param zio - Effect to run before each test
208
*/
209
def before[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
210
211
/**
212
* Run effect after each test
213
* @param zio - Effect to run after each test
214
*/
215
def after[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
216
217
/**
218
* Run effects before and after each test
219
* @param before - Effect to run before each test
220
* @param after - Effect to run after each test
221
*/
222
def around[R](before: ZIO[R, Nothing, Any], after: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
223
224
/**
225
* Run effect before all tests in a suite
226
* @param zio - Effect to run before all tests
227
*/
228
def beforeAll[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
229
230
/**
231
* Run effect after all tests in a suite
232
* @param zio - Effect to run after all tests
233
*/
234
def afterAll[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
235
236
/**
237
* Run effects before and after all tests in a suite
238
* @param before - Effect to run before all tests
239
* @param after - Effect to run after all tests
240
*/
241
def aroundAll[R](before: ZIO[R, Nothing, Any], after: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
242
243
/**
244
* Run effect after successful test
245
* @param zio - Effect to run after test success
246
*/
247
def afterSuccess[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
248
249
/**
250
* Run effect after failed test
251
* @param zio - Effect to run after test failure
252
*/
253
def afterFailure[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
254
255
/**
256
* Run effect after all tests succeed
257
* @param zio - Effect to run after all tests succeed
258
*/
259
def afterAllSuccess[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
260
261
/**
262
* Run effect after any test fails
263
* @param zio - Effect to run after any test fails
264
*/
265
def afterAllFailure[R](zio: ZIO[R, Nothing, Any]): TestAspect[Nothing, R, Nothing, Any]
266
```
267
268
**Usage Examples:**
269
270
```scala
271
import zio.test._
272
import zio.test.TestAspect._
273
274
// Database test with setup/teardown
275
suite("database tests")(
276
test("create user") { createUserTest() },
277
test("read user") { readUserTest() },
278
test("update user") { updateUserTest() },
279
test("delete user") { deleteUserTest() }
280
) @@ beforeAll(initializeDatabase()) @@ afterAll(cleanupDatabase())
281
282
// Individual test setup
283
test("file processing") {
284
processFile("test.txt")
285
} @@ before(createTestFile()) @@ after(deleteTestFile())
286
287
// Conditional cleanup
288
test("resource test") {
289
useResource()
290
} @@ afterSuccess(logSuccess()) @@ afterFailure(logFailure())
291
```
292
293
### Environment Aspects
294
295
Aspects for providing dependencies and environment to tests.
296
297
```scala { .api }
298
/**
299
* Provide layer to tests
300
* @param layer - ZLayer to provide
301
*/
302
def fromLayer[R](layer: ZLayer[Any, Nothing, R]): TestAspect[Nothing, R, Nothing, Any]
303
304
/**
305
* Provide shared layer to tests (layer is created once and reused)
306
* @param layer - ZLayer to provide and share
307
*/
308
def fromLayerShared[R](layer: ZLayer[Any, Nothing, R]): TestAspect[Nothing, R, Nothing, Any]
309
310
/**
311
* Use custom config provider
312
* @param provider - ConfigProvider to use
313
*/
314
def withConfigProvider(provider: ConfigProvider): TestAspectPoly
315
```
316
317
**Usage Examples:**
318
319
```scala
320
import zio.test._
321
import zio.test.TestAspect._
322
323
// Provide database layer to tests
324
suite("service tests")(
325
test("user service") {
326
for {
327
service <- ZIO.service[UserService]
328
user <- service.createUser("Alice")
329
} yield assertTrue(user.name == "Alice")
330
}
331
) @@ fromLayer(DatabaseLayer.test ++ UserService.live)
332
333
// Shared expensive resource
334
suite("integration tests")(
335
// Multiple tests using shared database connection
336
) @@ fromLayerShared(DatabaseConnectionPool.live)
337
338
// Custom configuration
339
suite("config tests")(
340
test("load config") {
341
for {
342
config <- ZIO.config[AppConfig]
343
} yield assertTrue(config.appName.nonEmpty)
344
}
345
) @@ withConfigProvider(ConfigProvider.fromMap(Map("appName" -> "TestApp")))
346
```
347
348
### Property Testing Configuration
349
350
Aspects for configuring property-based test parameters.
351
352
```scala { .api }
353
/**
354
* Set number of samples for property tests
355
* @param n - Number of samples to generate
356
*/
357
def samples(n: Int): TestAspectPoly
358
359
/**
360
* Set number of shrinking attempts
361
* @param n - Maximum shrinking iterations
362
*/
363
def shrinks(n: Int): TestAspectPoly
364
365
/**
366
* Set generator size parameter
367
* @param n - Size parameter for generators
368
*/
369
def size(n: Int): TestAspectPoly
370
371
/**
372
* Set generator size using function
373
* @param f - Function to transform size parameter
374
*/
375
def sized(f: Int => Int): TestAspectPoly
376
377
/**
378
* Set random seed for reproducible tests
379
* @param seed - Random seed value
380
*/
381
def setSeed(seed: Long): TestAspectPoly
382
```
383
384
**Usage Examples:**
385
386
```scala
387
import zio.test._
388
import zio.test.TestAspect._
389
390
test("property test with more samples") {
391
check(Gen.int) { n =>
392
assertTrue((n + 1) > n)
393
}
394
} @@ samples(10000)
395
396
test("property test with larger values") {
397
check(Gen.listOf(Gen.int)) { numbers =>
398
assertTrue(numbers.reverse.reverse == numbers)
399
}
400
} @@ size(1000)
401
402
test("reproducible property test") {
403
check(Gen.int) { n =>
404
assertTrue(n.isInstanceOf[Int])
405
}
406
} @@ setSeed(42L)
407
```
408
409
### Platform and Conditional Aspects
410
411
Aspects for conditional test execution based on platform or environment.
412
413
```scala { .api }
414
/**
415
* Run only if environment variable matches condition
416
* @param name - Environment variable name
417
* @param predicate - Condition to check variable value
418
*/
419
def ifEnv(name: String, predicate: String => Boolean): TestAspectPoly
420
421
/**
422
* Run only if environment variable is set
423
* @param name - Environment variable name
424
*/
425
def ifEnvSet(name: String): TestAspectPoly
426
427
/**
428
* Run only if environment variable is not set
429
* @param name - Environment variable name
430
*/
431
def ifEnvNotSet(name: String): TestAspectPoly
432
433
/**
434
* Run conditionally based on environment variable option
435
* @param name - Environment variable name
436
* @param predicate - Condition to check optional value
437
*/
438
def ifEnvOption(name: String, predicate: Option[String] => Boolean): TestAspectPoly
439
440
/**
441
* Run only if system property matches condition
442
* @param name - System property name
443
* @param predicate - Condition to check property value
444
*/
445
def ifProp(name: String, predicate: String => Boolean): TestAspectPoly
446
447
/**
448
* Run only if system property is set
449
* @param name - System property name
450
*/
451
def ifPropSet(name: String): TestAspectPoly
452
453
/**
454
* Run only if system property is not set
455
* @param name - System property name
456
*/
457
def ifPropNotSet(name: String): TestAspectPoly
458
459
/**
460
* Run conditionally based on system property option
461
* @param name - System property name
462
* @param predicate - Condition to check optional value
463
*/
464
def ifPropOption(name: String, predicate: Option[String] => Boolean): TestAspectPoly
465
466
/**
467
* Run only on JVM platform
468
*/
469
def jvmOnly: TestAspectPoly
470
471
/**
472
* Run only on JavaScript platform
473
*/
474
def jsOnly: TestAspectPoly
475
476
/**
477
* Run only on Scala Native platform
478
*/
479
def nativeOnly: TestAspectPoly
480
481
/**
482
* Run only on Unix systems
483
*/
484
def unix: TestAspectPoly
485
486
/**
487
* Run only on Windows systems
488
*/
489
def windows: TestAspectPoly
490
491
/**
492
* Run conditionally based on operating system
493
* @param predicate - Condition to check OS name
494
*/
495
def os(predicate: String => Boolean): TestAspectPoly
496
497
/**
498
* Run only on Scala 2
499
*/
500
def scala2Only: TestAspectPoly
501
502
/**
503
* Run only on Scala 3
504
*/
505
def scala3Only: TestAspectPoly
506
```
507
508
**Usage Examples:**
509
510
```scala
511
import zio.test._
512
import zio.test.TestAspect._
513
514
// Platform-specific tests
515
test("JVM-specific functionality") {
516
// JVM-only code
517
assertTrue(System.getProperty("java.version").nonEmpty)
518
} @@ jvmOnly
519
520
test("Unix file permissions") {
521
// Unix-specific test
522
assertTrue(java.io.File.separatorChar == '/')
523
} @@ unix
524
525
// Environment-conditional tests
526
test("production database test") {
527
// Only run if ENVIRONMENT=production
528
connectToDatabase()
529
} @@ ifEnv("ENVIRONMENT", _ == "production")
530
531
test("CI-only test") {
532
// Only run in CI environment
533
runExpensiveTest()
534
} @@ ifEnvSet("CI")
535
536
// Scala version specific
537
test("Scala 3 feature") {
538
// Test Scala 3 specific features
539
assertTrue(true)
540
} @@ scala3Only
541
```
542
543
### State Management Aspects
544
545
Aspects for saving and restoring test service state.
546
547
```scala { .api }
548
/**
549
* Restore using custom restorable
550
* @param restorable - Custom restorable implementation
551
*/
552
def restore[R](restorable: Restorable[R]): TestAspect[Nothing, R, Nothing, Any]
553
554
/**
555
* Restore test clock state after each test
556
*/
557
def restoreTestClock: TestAspectPoly
558
559
/**
560
* Restore test console state after each test
561
*/
562
def restoreTestConsole: TestAspectPoly
563
564
/**
565
* Restore test random state after each test
566
*/
567
def restoreTestRandom: TestAspectPoly
568
569
/**
570
* Restore test system state after each test
571
*/
572
def restoreTestSystem: TestAspectPoly
573
574
/**
575
* Restore entire test environment state after each test
576
*/
577
def restoreTestEnvironment: TestAspectPoly
578
```
579
580
**Usage Examples:**
581
582
```scala
583
import zio.test._
584
import zio.test.TestAspect._
585
586
suite("clock tests")(
587
test("advance time") {
588
TestClock.adjust(1.hour) *>
589
assertTrue(true)
590
},
591
test("time is reset") {
592
// Clock state restored from previous test
593
for {
594
time <- Clock.instant
595
} yield assertTrue(time.getEpochSecond == 0)
596
}
597
) @@ restoreTestClock
598
599
suite("console tests")(
600
test("write output") {
601
Console.printLine("test output") *>
602
assertTrue(true)
603
},
604
test("output is cleared") {
605
// Console state restored from previous test
606
for {
607
output <- TestConsole.output
608
} yield assertTrue(output.isEmpty)
609
}
610
) @@ restoreTestConsole
611
```
612
613
### Diagnostic and Analysis Aspects
614
615
Aspects for debugging and analyzing test execution.
616
617
```scala { .api }
618
/**
619
* Add diagnostic information to test execution
620
* @param duration - Duration to wait before showing diagnostics
621
*/
622
def diagnose(duration: Duration): TestAspectPoly
623
624
/**
625
* Suppress test output (run silently)
626
*/
627
def silent: TestAspectPoly
628
629
/**
630
* Add tags to tests for filtering and organization
631
* @param tag - Primary tag
632
* @param tags - Additional tags
633
*/
634
def tag(tag: String, tags: String*): TestAspectPoly
635
```
636
637
### Verification Aspects
638
639
Aspects for adding verification conditions to tests.
640
641
```scala { .api }
642
/**
643
* Add verification condition to test results
644
* @param assertion - Assertion to verify against test result
645
*/
646
def verify(assertion: Assertion[TestResult]): TestAspectPoly
647
648
/**
649
* Expect test to fail with specific condition
650
* @param assertion - Assertion to verify against test failure
651
*/
652
def failing[E](assertion: Assertion[TestFailure[E]]): TestAspect[Nothing, Any, E, Nothing]
653
```
654
655
**Usage Examples:**
656
657
```scala
658
import zio.test._
659
import zio.test.TestAspect._
660
661
test("expected failure") {
662
// This test is expected to fail
663
assertTrue(false)
664
} @@ failing(anything)
665
666
test("performance verification") {
667
expensiveOperation()
668
} @@ verify(anything) @@ diagnose(5.seconds)
669
670
// Tagged tests for organization
671
suite("integration tests")(
672
test("database test") { dbTest() } @@ tag("db", "integration"),
673
test("api test") { apiTest() } @@ tag("api", "integration"),
674
test("auth test") { authTest() } @@ tag("auth", "integration")
675
)
676
```
677
678
### Aspect Composition
679
680
Methods for combining and composing aspects.
681
682
```scala { .api }
683
// Combine aspects using && operator
684
val combinedAspect = timeout(30.seconds) && parallel && samples(1000)
685
686
// Chain aspects using >>> operator
687
val chainedAspect = beforeAll(setup()) >>> timeout(1.minute) >>> afterAll(cleanup())
688
689
// Apply multiple aspects to test
690
test("complex test") {
691
complexOperation()
692
} @@ timeout(1.minute) @@ flaky(3) @@ restoreTestClock @@ tag("slow", "integration")
693
```
694
695
**Usage Examples:**
696
697
```scala
698
import zio.test._
699
import zio.test.TestAspect._
700
701
// Complex aspect composition
702
val integrationTestAspect =
703
beforeAll(setupIntegrationEnvironment()) @@
704
afterAll(cleanupIntegrationEnvironment()) @@
705
timeout(5.minutes) @@
706
flaky(2) @@
707
tag("integration") @@
708
ifEnvSet("RUN_INTEGRATION_TESTS")
709
710
suite("integration tests")(
711
test("end-to-end workflow") { e2eTest() },
712
test("external service integration") { externalServiceTest() }
713
) @@ integrationTestAspect
714
```