0
# Test Aspects
1
2
Composable test transformations for modifying test behavior including retry, timeout, and parallel execution.
3
4
## Capabilities
5
6
### TestAspect Class
7
8
Core abstraction for transforming test behavior.
9
10
```scala { .api }
11
/**
12
* Test aspect that transforms 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
abstract class TestAspect[+LowerR, -UpperR, +LowerE, -UpperE] {
19
/** Applies aspect to test specification */
20
def apply[R, E](spec: ZSpec[R, E]): ZSpec[R, E]
21
22
/** Composes with another aspect */
23
def andThen[LowerR1, UpperR1, LowerE1, UpperE1](
24
that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
25
): TestAspect[LowerR with LowerR1, UpperR1, LowerE with LowerE1, UpperE1]
26
27
/** Alias for andThen */
28
def >>>[LowerR1, UpperR1, LowerE1, UpperE1](
29
that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]
30
): TestAspect[LowerR with LowerR1, UpperR1, LowerE with LowerE1, UpperE1]
31
}
32
```
33
34
### Lifecycle Aspects
35
36
Aspects that run effects before, after, or around test execution.
37
38
```scala { .api }
39
/** Runs effect before test execution */
40
def before[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
41
42
/** Runs effect after test execution */
43
def after[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
44
45
/** Runs effect after test success */
46
def afterSuccess[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
47
48
/** Runs effect after test failure */
49
def afterFailure[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
50
51
/** Runs effects before all tests in suite */
52
def beforeAll[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
53
54
/** Runs effects after all tests in suite */
55
def afterAll[R](effect: ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
56
57
/** Brackets test execution with setup and cleanup */
58
def around[R, A](
59
before: ZIO[R, Nothing, A]
60
)(after: A => ZIO[R, Nothing, Any]): TestAspectAtLeastR[R]
61
```
62
63
### Execution Control Aspects
64
65
Aspects that control how and when tests are executed.
66
67
```scala { .api }
68
/** Ignores test (marks as skipped) */
69
val ignore: TestAspectPoly
70
71
/** Runs test exactly once */
72
val once: TestAspectPoly
73
74
/** Runs tests in parallel */
75
val parallel: TestAspectPoly
76
77
/** Runs tests sequentially */
78
val sequential: TestAspectPoly
79
80
/** Sets test timeout */
81
def timeout(duration: Duration): TestAspectPoly
82
83
/** Warns about slow tests */
84
def diagnose(duration: Duration): TestAspectPoly
85
86
/** Warns if test takes longer than duration */
87
def timeoutWarning(duration: Duration): TestAspectPoly
88
```
89
90
### Retry and Flaky Test Aspects
91
92
Aspects for handling unreliable or flaky tests.
93
94
```scala { .api }
95
/** Marks test as flaky (may fail occasionally) */
96
val flaky: TestAspectPoly
97
98
/** Marks test as non-flaky (must always pass) */
99
val nonFlaky: TestAspectPoly
100
101
/** Retries test until success (eventually) */
102
val eventually: TestAspectPoly
103
104
/** Retries test N times on failure */
105
def retryN(n: Int): TestAspectPoly
106
107
/** Retries test according to schedule */
108
def retry[R](schedule: Schedule[R, TestFailure[Any], Any]): TestAspectAtLeastR[R]
109
110
/** Requires test to succeed (no retries) */
111
val success: TestAspectPoly
112
```
113
114
### Repetition Aspects
115
116
Aspects for running tests multiple times.
117
118
```scala { .api }
119
/** Repeats test N times */
120
def repeatN(n: Int): TestAspectPoly
121
122
/** Repeats test according to schedule */
123
def repeat[R](schedule: Schedule[R, TestResult, Any]): TestAspectAtLeastR[R]
124
```
125
126
### Configuration Aspects
127
128
Aspects that modify test environment or configuration.
129
130
```scala { .api }
131
/** Sets generator size for property tests */
132
def sized(size: Int): TestAspectPoly
133
134
/** Sets number of shrinking attempts */
135
def shrinks(n: Int): TestAspectPoly
136
137
/** Sets number of samples for property tests */
138
def samples(n: Int): TestAspectPoly
139
140
/** Adds tags to test */
141
def tag(tag: String, tags: String*): TestAspectPoly
142
143
/** Times test execution */
144
val timed: TestAspectPoly
145
```
146
147
### Platform-Specific Aspects
148
149
Aspects that control test execution on different platforms.
150
151
```scala { .api }
152
/** Runs test only on JVM */
153
val jvmOnly: TestAspectPoly
154
155
/** Runs test only on JavaScript */
156
val jsOnly: TestAspectPoly
157
158
/** Runs test only on Scala Native */
159
val nativeOnly: TestAspectPoly
160
161
/** Runs test only on specified platforms */
162
def platformSpecific[R, E, A](
163
js: => A,
164
jvm: => A
165
)(f: A => ZTest[R, E]): ZTest[R, E]
166
```
167
168
### Resource Management Aspects
169
170
Aspects for managing resources during test execution.
171
172
```scala { .api }
173
/** Restores test state after execution */
174
def restore[R](f: ZIO[R, Nothing, TestResult] => ZIO[R, Nothing, TestResult]): TestAspectAtLeastR[R]
175
176
/** Provides managed resource to test */
177
def aroundManaged[R, A](managed: Managed[Nothing, A]): TestAspectAtLeastR[R]
178
179
/** Provides layer to test */
180
def provideLayer[R0, R, E](layer: ZLayer[R0, E, R]): TestAspect[Nothing, R0, E, Any]
181
182
/** Provides custom layer to test */
183
def provideCustomLayer[R0, R, E](layer: ZLayer[R0, E, R]): TestAspect[Nothing, R0, E, Any]
184
```
185
186
## Type Aliases
187
188
```scala { .api }
189
/**
190
* Test aspect that requires at least environment R
191
*/
192
type TestAspectAtLeastR[R] = TestAspect[Nothing, R, Nothing, Any]
193
194
/**
195
* Polymorphic test aspect with no environment or error constraints
196
*/
197
type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]
198
```
199
200
## Usage Examples
201
202
### Basic Aspect Application
203
204
```scala
205
import zio.test._
206
import zio.duration._
207
208
// Apply single aspect
209
test("flaky test") {
210
assert(scala.util.Random.nextBoolean())(isTrue)
211
} @@ TestAspect.flaky
212
213
// Apply multiple aspects
214
test("complex test") {
215
assert(2 + 2)(equalTo(4))
216
} @@ TestAspect.timeout(30.seconds) @@ TestAspect.retryN(3)
217
218
// Apply to entire suite
219
suite("Parallel Suite")(
220
test("test 1")(assert(true)(isTrue)),
221
test("test 2")(assert(true)(isTrue))
222
) @@ TestAspect.parallel
223
```
224
225
### Lifecycle Management
226
227
```scala
228
suite("Database Tests")(
229
test("insert user") {
230
// test implementation
231
assert(true)(isTrue)
232
},
233
234
test("update user") {
235
// test implementation
236
assert(true)(isTrue)
237
}
238
) @@ TestAspect.beforeAll(initializeDatabase) @@
239
TestAspect.afterAll(cleanupDatabase)
240
241
// Per-test lifecycle
242
test("isolated test") {
243
assert(true)(isTrue)
244
} @@ TestAspect.around(createTestData)(cleanupTestData)
245
```
246
247
### Retry and Flaky Tests
248
249
```scala
250
import zio.test._
251
import zio.schedule._
252
253
// Simple retry
254
test("unstable network call") {
255
// test that might fail due to network issues
256
assert(true)(isTrue)
257
} @@ TestAspect.retryN(5)
258
259
// Eventually succeeds (keeps retrying)
260
test("eventually passing") {
261
assert(scala.util.Random.nextInt(10))(isGreaterThan(8))
262
} @@ TestAspect.eventually
263
264
// Custom retry schedule
265
test("scheduled retry") {
266
assert(true)(isTrue)
267
} @@ TestAspect.retry(Schedule.exponential(100.millis) && Schedule.recurs(3))
268
269
// Mark as flaky but still run
270
test("known flaky test") {
271
assert(System.currentTimeMillis() % 2 == 0)(isTrue)
272
} @@ TestAspect.flaky
273
```
274
275
### Platform-Specific Tests
276
277
```scala
278
suite("Platform Tests")(
279
test("JVM specific feature") {
280
// test JVM-only functionality
281
assert(true)(isTrue)
282
} @@ TestAspect.jvmOnly,
283
284
test("JavaScript specific feature") {
285
// test JS-only functionality
286
assert(true)(isTrue)
287
} @@ TestAspect.jsOnly,
288
289
test("cross-platform feature") {
290
assert(2 + 2)(equalTo(4))
291
}
292
)
293
```
294
295
### Performance and Timing
296
297
```scala
298
suite("Performance Tests")(
299
test("fast operation") {
300
// should complete quickly
301
assert(true)(isTrue)
302
} @@ TestAspect.timeout(1.second),
303
304
test("operation with timing") {
305
Thread.sleep(100)
306
assert(true)(isTrue)
307
} @@ TestAspect.timed,
308
309
test("slow operation warning") {
310
Thread.sleep(50)
311
assert(true)(isTrue)
312
} @@ TestAspect.diagnose(10.millis)
313
)
314
```
315
316
### Property Testing Configuration
317
318
```scala
319
import zio.test.Gen._
320
321
test("property test configuration") {
322
check(int) { n =>
323
assert(n * 2)(equalTo(n + n))
324
}
325
} @@ TestAspect.samples(1000) @@
326
TestAspect.shrinks(100) @@
327
TestAspect.sized(50)
328
```
329
330
### Resource Management
331
332
```scala
333
val databaseLayer = ZLayer.fromManaged {
334
Managed.make(initDatabase)(_.close())
335
}
336
337
suite("Database Integration Tests")(
338
test("query users") {
339
// test using database
340
assert(true)(isTrue)
341
},
342
343
test("insert user") {
344
// test using database
345
assert(true)(isTrue)
346
}
347
) @@ TestAspect.provideCustomLayer(databaseLayer)
348
```
349
350
### Custom Aspects
351
352
```scala
353
// Custom aspect that logs test execution
354
def logged[R <: TestLogger](message: String): TestAspectAtLeastR[R] =
355
TestAspect.before(TestLogger.logLine(s"Starting: $message")) >>>
356
TestAspect.after(TestLogger.logLine(s"Finished: $message"))
357
358
// Usage
359
test("logged test") {
360
assert(true)(isTrue)
361
} @@ logged("my important test")
362
363
// Aspect composition
364
val resilientTest = TestAspect.retryN(3) >>>
365
TestAspect.timeout(10.seconds) >>>
366
TestAspect.diagnose(1.second)
367
368
test("resilient test") {
369
assert(true)(isTrue)
370
} @@ resilientTest
371
```
372
373
### Conditional Aspects
374
375
```scala
376
// Apply aspect conditionally
377
def whenCI(aspect: TestAspectPoly): TestAspectPoly =
378
if (sys.env.contains("CI")) aspect else TestAspect.identity
379
380
test("CI test") {
381
assert(true)(isTrue)
382
} @@ whenCI(TestAspect.retryN(5))
383
384
// Environment-based aspects
385
test("development test") {
386
assert(true)(isTrue)
387
} @@ TestAspect.ifEnv("ENV", "dev")(TestAspect.ignore)
388
```