0
# Configuration and Environment
1
2
Configuration system for controlling test execution parameters and environment setup for dependency injection.
3
4
## Capabilities
5
6
### TestConfig
7
8
Configuration service that controls various aspects of test execution.
9
10
```scala { .api }
11
/**
12
* Configuration for test execution parameters
13
*/
14
trait TestConfig {
15
/**
16
* Number of times to repeat each test
17
* @return number of repetitions
18
*/
19
def repeats: Int
20
21
/**
22
* Number of times to retry failed tests
23
* @return number of retries
24
*/
25
def retries: Int
26
27
/**
28
* Number of samples for property-based tests
29
* @return number of samples to generate
30
*/
31
def samples: Int
32
33
/**
34
* Maximum shrinking attempts for failed property tests
35
* @return maximum shrinking attempts
36
*/
37
def shrinks: Int
38
39
/**
40
* Size parameter for generators
41
* @return size value for Sized environment
42
*/
43
def size: Int
44
}
45
46
object TestConfig {
47
/**
48
* Create test configuration with specified parameters
49
* @param repeats number of test repetitions
50
* @param retries number of retry attempts
51
* @param samples number of property test samples
52
* @param shrinks maximum shrinking attempts
53
* @param size generator size parameter
54
* @return layer providing test configuration
55
*/
56
def live(
57
repeats: Int,
58
retries: Int,
59
samples: Int,
60
shrinks: Int,
61
size: Int
62
): ULayer[TestConfig]
63
64
/**
65
* Default test configuration
66
* - repeats: 1
67
* - retries: 0
68
* - samples: 200
69
* - shrinks: 1000
70
* - size: 100
71
*/
72
val default: ULayer[TestConfig]
73
74
/**
75
* Access number of repeats from configuration
76
*/
77
val repeats: URIO[TestConfig, Int]
78
79
/**
80
* Access number of retries from configuration
81
*/
82
val retries: URIO[TestConfig, Int]
83
84
/**
85
* Access number of samples from configuration
86
*/
87
val samples: URIO[TestConfig, Int]
88
89
/**
90
* Access number of shrinks from configuration
91
*/
92
val shrinks: URIO[TestConfig, Int]
93
94
/**
95
* Access size parameter from configuration
96
*/
97
val size: URIO[TestConfig, Int]
98
}
99
```
100
101
**Usage Examples:**
102
103
```scala
104
import zio.test._
105
import zio._
106
107
// Custom test configuration
108
val customConfig = TestConfig.live(
109
repeats = 5, // Run each test 5 times
110
retries = 2, // Retry failed tests twice
111
samples = 1000, // Use 1000 samples for property tests
112
shrinks = 500, // Maximum 500 shrinking attempts
113
size = 200 // Generator size of 200
114
)
115
116
// Using configuration in tests
117
test("property test with custom config").provideLayer(customConfig) {
118
check(Gen.listOf(Gen.anyInt)) { list =>
119
assertTrue(list.reverse.reverse == list)
120
}
121
}
122
123
// Access configuration values
124
test("configuration access") {
125
for {
126
samples <- TestConfig.samples
127
size <- TestConfig.size
128
} yield assertTrue(samples > 0 && size > 0)
129
}
130
```
131
132
### TestEnvironment
133
134
Combined environment type containing all standard test services.
135
136
```scala { .api }
137
/**
138
* Standard test environment combining all test services
139
*/
140
type TestEnvironment = Annotations with Live with Sized with TestConfig
141
142
object TestEnvironment {
143
/**
144
* Create test environment from live system services
145
* @return layer providing complete test environment
146
*/
147
val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]
148
149
/**
150
* Access any service from test environment
151
* @return layer that provides access to the environment
152
*/
153
val any: ZLayer[TestEnvironment, Nothing, TestEnvironment]
154
}
155
156
/**
157
* Layer providing live system services for test environment creation
158
*/
159
val liveEnvironment: Layer[Nothing, Clock with Console with System with Random]
160
161
/**
162
* Complete test environment layer (recommended for most tests)
163
*/
164
val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]
165
```
166
167
**Usage Examples:**
168
169
```scala
170
import zio.test._
171
import zio._
172
173
// Using default test environment
174
object MyTestSpec extends ZIOSpecDefault {
175
def spec = suite("My Tests")(
176
test("basic test") {
177
// Has access to full TestEnvironment
178
for {
179
time <- Clock.currentTime(TimeUnit.MILLISECONDS)
180
_ <- Console.printLine(s"Current time: $time")
181
random <- Random.nextInt(100)
182
} yield assertTrue(time > 0 && random >= 0 && random < 100)
183
}
184
)
185
}
186
187
// Custom environment combination
188
val customTestEnv: ZLayer[Any, Nothing, TestEnvironment] =
189
ZLayer.make[TestEnvironment](
190
TestClock.default,
191
TestConsole.debug,
192
TestRandom.deterministic(12345L),
193
TestSystem.default,
194
Annotations.live,
195
Live.default,
196
Sized.live(50),
197
TestConfig.live(1, 0, 500, 1000, 50)
198
)
199
200
object CustomEnvSpec extends ZIOSpec[TestEnvironment] {
201
def spec = suite("Custom Environment Tests")(
202
test("uses custom environment") {
203
for {
204
size <- Sized.size
205
samples <- TestConfig.samples
206
} yield assertTrue(size == 50 && samples == 500)
207
}
208
).provideLayer(customTestEnv)
209
}
210
```
211
212
### TestArgs
213
214
Command-line argument parsing for test runners.
215
216
```scala { .api }
217
/**
218
* Command-line arguments for test execution
219
*/
220
case class TestArgs(
221
testSearchTerms: List[String] = Nil,
222
tagSearchTerms: List[String] = Nil,
223
testTaskPolicy: TestTaskPolicy = TestTaskPolicy.Sequential,
224
225
// Execution parameters
226
repeats: Option[Int] = None,
227
retries: Option[Int] = None,
228
samples: Option[Int] = None,
229
shrinks: Option[Int] = None,
230
size: Option[Int] = None,
231
232
// Output control
233
verbose: Boolean = false,
234
color: Boolean = true,
235
summary: Boolean = true
236
)
237
238
object TestArgs {
239
/**
240
* Parse command-line arguments
241
* @param args command-line argument list
242
* @return parsed test arguments
243
*/
244
def parse(args: List[String]): Either[String, TestArgs]
245
246
/**
247
* Parse command-line arguments with defaults
248
* @param args command-line argument list
249
* @param defaults default test arguments
250
* @return parsed test arguments
251
*/
252
def parse(args: List[String], defaults: TestArgs): Either[String, TestArgs]
253
254
/**
255
* Empty test arguments (all defaults)
256
*/
257
val empty: TestArgs
258
}
259
260
/**
261
* Test execution policy
262
*/
263
sealed trait TestTaskPolicy
264
object TestTaskPolicy {
265
case object Sequential extends TestTaskPolicy
266
case object Parallel extends TestTaskPolicy
267
case class ParallelN(n: Int) extends TestTaskPolicy
268
}
269
```
270
271
**Usage Examples:**
272
273
```scala
274
import zio.test._
275
276
// Parsing command-line arguments
277
val args = List("--samples", "1000", "--parallel", "--verbose", "UserTests")
278
val testArgs = TestArgs.parse(args) match {
279
case Right(args) => args
280
case Left(error) => throw new IllegalArgumentException(error)
281
}
282
283
// Using parsed arguments to configure tests
284
val configFromArgs = testArgs.samples.fold(TestConfig.default) { samples =>
285
TestConfig.live(
286
repeats = testArgs.repeats.getOrElse(1),
287
retries = testArgs.retries.getOrElse(0),
288
samples = samples,
289
shrinks = testArgs.shrinks.getOrElse(1000),
290
size = testArgs.size.getOrElse(100)
291
)
292
}
293
```
294
295
### Annotations
296
297
Service for attaching metadata to tests.
298
299
```scala { .api }
300
/**
301
* Service for managing test annotations and metadata
302
*/
303
trait Annotations {
304
/**
305
* Get annotation value by key
306
* @param key annotation key
307
* @return effect producing optional annotation value
308
*/
309
def get[V](key: TestAnnotation[V]): UIO[V]
310
311
/**
312
* Annotate with key-value pair
313
* @param key annotation key
314
* @param value annotation value
315
* @return effect that adds the annotation
316
*/
317
def annotate[V](key: TestAnnotation[V], value: V): UIO[Unit]
318
319
/**
320
* Get all current annotations
321
* @return effect producing annotation map
322
*/
323
def annotationMap: UIO[TestAnnotationMap]
324
325
/**
326
* Supervise effect with annotation inheritance
327
* @param zio effect to supervise
328
* @return supervised effect with annotations
329
*/
330
def supervisedFibers[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
331
}
332
333
object Annotations {
334
/**
335
* Live annotations implementation
336
*/
337
val live: ULayer[Annotations]
338
}
339
340
/**
341
* Test annotation key-value pair
342
*/
343
trait TestAnnotation[V] {
344
/**
345
* Annotation identifier
346
*/
347
def identifier: String
348
349
/**
350
* Initial/default value
351
*/
352
def initial: V
353
354
/**
355
* Combine two annotation values
356
* @param v1 first value
357
* @param v2 second value
358
* @return combined value
359
*/
360
def combine(v1: V, v2: V): V
361
}
362
363
object TestAnnotation {
364
/**
365
* Create custom annotation
366
* @param name annotation name
367
* @param initialValue initial value
368
* @param combineFunction value combination function
369
* @return annotation instance
370
*/
371
def apply[V](
372
name: String,
373
initialValue: V,
374
combineFunction: (V, V) => V
375
): TestAnnotation[V]
376
377
// Built-in annotations
378
val ignored: TestAnnotation[Boolean]
379
val repeated: TestAnnotation[Int]
380
val retried: TestAnnotation[Int]
381
val tagged: TestAnnotation[Set[String]]
382
val timing: TestAnnotation[Duration]
383
}
384
```
385
386
**Usage Examples:**
387
388
```scala
389
import zio.test._
390
import zio._
391
392
// Using built-in annotations
393
test("annotated test") {
394
for {
395
_ <- Annotations.annotate(TestAnnotation.tagged, Set("slow", "integration"))
396
_ <- Annotations.annotate(TestAnnotation.repeated, 5)
397
annotations <- Annotations.annotationMap
398
} yield assertTrue(annotations.get(TestAnnotation.tagged).contains("slow"))
399
}
400
401
// Custom annotations
402
val databaseAnnotation = TestAnnotation[String](
403
"database",
404
"none",
405
(_, newer) => newer // Use newer value
406
)
407
408
test("database test") {
409
for {
410
_ <- Annotations.annotate(databaseAnnotation, "postgresql")
411
dbType <- Annotations.get(databaseAnnotation)
412
} yield assertTrue(dbType == "postgresql")
413
}
414
```
415
416
### Live Service
417
418
Service for executing effects with live (production) implementations.
419
420
```scala { .api }
421
/**
422
* Service for accessing live implementations of system services
423
*/
424
trait Live {
425
/**
426
* Execute effect with live service implementations
427
* @param zio effect to execute with live services
428
* @return effect executed with live services
429
*/
430
def provide[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
431
}
432
433
object Live {
434
/**
435
* Default live service implementation
436
*/
437
val default: ULayer[Live]
438
439
/**
440
* Execute effect with live services (convenience function)
441
* @param zio effect to execute
442
* @return effect with live services
443
*/
444
def live[R, E, A](zio: ZIO[R, E, A]): ZIO[R with Live, E, A]
445
446
/**
447
* Transform effect to use live services for outer effect while preserving
448
* test environment for inner effect
449
* @param zio inner effect using test environment
450
* @param f transformation using live environment
451
* @return transformed effect
452
*/
453
def withLive[R, E, E1, A, B](zio: ZIO[R, E, A])(
454
f: ZIO[R, E, A] => ZIO[R, E1, B]
455
): ZIO[R with Live, E1, B]
456
}
457
```
458
459
**Usage Examples:**
460
461
```scala
462
import zio.test._
463
import zio._
464
465
// Execute with live services when needed
466
test("performance test with real time") {
467
for {
468
// Use test clock for setup
469
testClock <- ZIO.service[TestClock]
470
_ <- testClock.setTime(Duration.zero)
471
472
// Use live clock for actual measurement
473
result <- Live.live(
474
for {
475
start <- Clock.currentTime(TimeUnit.MILLISECONDS)
476
_ <- ZIO.sleep(100.millis) // Actually sleep
477
end <- Clock.currentTime(TimeUnit.MILLISECONDS)
478
} yield end - start
479
)
480
} yield assertTrue(result >= 100L) // Real time elapsed
481
}
482
483
// Mix test and live environments
484
test("hybrid test environment") {
485
Live.withLive(
486
// This runs with test environment
487
for {
488
_ <- Console.printLine("Test output") // Goes to test console
489
value <- Random.nextInt(100) // Uses test random
490
} yield value
491
) { effect =>
492
// This transformation uses live environment
493
for {
494
_ <- Console.printLine("Live output") // Goes to real console
495
result <- effect // Execute inner effect with test env
496
} yield result
497
}
498
}
499
```
500
501
### Environment Manipulation
502
503
Functions for working with and modifying test environments.
504
505
```scala { .api }
506
/**
507
* Execute with modified annotations
508
* @param annotations new annotations service
509
* @param zio effect to execute
510
* @return effect with modified annotations
511
*/
512
def withAnnotations[R, E, A <: Annotations, B](annotations: => A)(
513
zio: => ZIO[R, E, B]
514
): ZIO[R, E, B]
515
516
/**
517
* Execute with modified test configuration
518
* @param testConfig new test configuration
519
* @param zio effect to execute
520
* @return effect with modified configuration
521
*/
522
def withTestConfig[R, E, A <: TestConfig, B](testConfig: => A)(
523
zio: => ZIO[R, E, B]
524
): ZIO[R, E, B]
525
526
/**
527
* Execute with modified size configuration
528
* @param sized new sized service
529
* @param zio effect to execute
530
* @return effect with modified size
531
*/
532
def withSized[R, E, A <: Sized, B](sized: => A)(
533
zio: => ZIO[R, E, B]
534
): ZIO[R, E, B]
535
536
/**
537
* Execute with modified live service
538
* @param live new live service
539
* @param zio effect to execute
540
* @return effect with modified live service
541
*/
542
def withLive[R, E, A <: Live, B](live: => A)(
543
zio: => ZIO[R, E, B]
544
): ZIO[R, E, B]
545
```
546
547
**Usage Examples:**
548
549
```scala
550
import zio.test._
551
import zio._
552
553
// Temporarily modify test configuration
554
test("high sample count test") {
555
withTestConfig(TestConfig.live(1, 0, 10000, 5000, 200)) {
556
check(Gen.listOf(Gen.anyInt)) { list =>
557
assertTrue(list.reverse.reverse == list)
558
}
559
}
560
}
561
562
// Temporarily modify generator size
563
test("large data structures") {
564
withSized(Sized(1000)) {
565
check(Gen.listOf(Gen.anyString)) { largeList =>
566
assertTrue(largeList.size <= 1000)
567
}
568
}
569
}
570
571
// Environment scoped modifications
572
test("scoped environment changes") {
573
for {
574
// Normal size
575
normalList <- Gen.listOf(Gen.anyInt).sample.runHead
576
_ <- ZIO.scoped {
577
withSizedScoped(Sized(10)) {
578
// Large size in this scope
579
Gen.listOf(Gen.anyInt).sample.runHead.map { largeList =>
580
// Compare sizes
581
assertTrue(largeList.exists(_.size > normalList.map(_.size).getOrElse(0)))
582
}
583
}
584
}
585
} yield assertTrue(true)
586
}
587
```