0
# Test Services
1
2
Controllable test environment services that replace real services with deterministic, testable implementations. Test services enable predictable testing of time-dependent, I/O-dependent, and stateful code.
3
4
## Capabilities
5
6
### Test Environment
7
8
Core test environment type and management functions.
9
10
```scala { .api }
11
/**
12
* Standard test environment combining all test services
13
*/
14
type TestEnvironment = Annotations with Live with Sized with TestConfig
15
16
object TestEnvironment {
17
/** Layer providing TestEnvironment from existing environment */
18
val any: ZLayer[TestEnvironment, Nothing, TestEnvironment]
19
20
/** Layer providing TestEnvironment from live services */
21
val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]
22
}
23
24
/**
25
* Live environment layer providing real services
26
*/
27
val liveEnvironment: Layer[Nothing, Clock with Console with System with Random]
28
29
/**
30
* Test environment layer providing all test services
31
*/
32
val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]
33
```
34
35
**Usage Examples:**
36
37
```scala
38
import zio.test._
39
40
// Using test environment in specs
41
object MyTest extends ZIOSpecDefault {
42
def spec = suite("Test with environment")(
43
test("time-based test") {
44
for {
45
_ <- TestClock.adjust(1.hour)
46
time <- Clock.instant
47
} yield assertTrue(time.getEpochSecond > 0)
48
}
49
)
50
}
51
52
// Custom environment combining test and domain services
53
type MyTestEnv = TestEnvironment with MyService
54
55
val myTestLayer: ZLayer[Any, Nothing, MyTestEnv] =
56
testEnvironment ++ MyService.test
57
```
58
59
### TestClock Service
60
61
Controllable clock service for testing time-dependent operations.
62
63
```scala { .api }
64
/**
65
* Controllable clock for testing time-dependent code
66
*/
67
trait TestClock extends Clock {
68
/** Adjust the test clock by the specified duration */
69
def adjust(duration: Duration)(implicit trace: Trace): UIO[Unit]
70
71
/** Set the test clock to a specific instant */
72
def setTime(instant: Instant)(implicit trace: Trace): UIO[Unit]
73
74
/** Get all pending scheduled operations */
75
def sleeps(implicit trace: Trace): UIO[List[Duration]]
76
77
/** Save the current clock state */
78
def save(implicit trace: Trace): UIO[TestClock.Data]
79
80
/** Restore the clock to a previous state */
81
def restore(data: TestClock.Data)(implicit trace: Trace): UIO[Unit]
82
}
83
84
object TestClock {
85
/** Default TestClock layer */
86
val default: ZLayer[Any, Nothing, TestClock]
87
88
/** Data representing clock state for save/restore */
89
case class Data(instant: Instant, sleeps: List[Duration])
90
}
91
92
/**
93
* Access TestClock service
94
*/
95
def testClock(implicit trace: Trace): UIO[TestClock]
96
97
/**
98
* Access TestClock service and run workflow
99
*/
100
def testClockWith[R, E, A](f: TestClock => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
101
```
102
103
**Usage Examples:**
104
105
```scala
106
import zio.test._
107
108
test("time-dependent operations") {
109
for {
110
// Start a timed operation
111
fiber <- ZIO.sleep(1.hour).fork
112
113
// Verify it hasn't completed yet
114
isDone <- fiber.isDone
115
_ <- assertTrue(!isDone)
116
117
// Advance time
118
_ <- TestClock.adjust(1.hour)
119
120
// Verify operation completed
121
_ <- fiber.join
122
isDone <- fiber.isDone
123
} yield assertTrue(isDone)
124
}
125
126
test("clock manipulation") {
127
for {
128
// Set specific time
129
_ <- TestClock.setTime(Instant.ofEpochSecond(0))
130
131
// Check current time
132
now <- Clock.instant
133
_ <- assertTrue(now.getEpochSecond == 0)
134
135
// Advance time by specific amount
136
_ <- TestClock.adjust(Duration.ofMinutes(30))
137
138
// Verify time advanced
139
later <- Clock.instant
140
} yield assertTrue(later.getEpochSecond == 30 * 60)
141
}
142
```
143
144
### TestConsole Service
145
146
Controllable console service for testing I/O operations.
147
148
```scala { .api }
149
/**
150
* Controllable console for testing I/O operations
151
*/
152
trait TestConsole extends Console {
153
/** Feed input to the console for reading */
154
def feedLines(lines: String*)(implicit trace: Trace): UIO[Unit]
155
156
/** Get all output written to console */
157
def output(implicit trace: Trace): UIO[Vector[String]]
158
159
/** Clear all console output */
160
def clearOutput(implicit trace: Trace): UIO[Unit]
161
162
/** Get all error output written to console */
163
def errorOutput(implicit trace: Trace): UIO[Vector[String]]
164
165
/** Clear all console error output */
166
def clearErrorOutput(implicit trace: Trace): UIO[Unit]
167
168
/** Save the current console state */
169
def save(implicit trace: Trace): UIO[TestConsole.Data]
170
171
/** Restore console to previous state */
172
def restore(data: TestConsole.Data)(implicit trace: Trace): UIO[Unit]
173
}
174
175
object TestConsole {
176
/** Default TestConsole layer */
177
val default: ZLayer[Any, Nothing, TestConsole]
178
179
/** Debug TestConsole layer that prints to real console */
180
val debug: ZLayer[Any, Nothing, TestConsole]
181
182
/** Data representing console state for save/restore */
183
case class Data(input: List[String], output: Vector[String], errorOutput: Vector[String])
184
}
185
186
/**
187
* Access TestConsole service
188
*/
189
def testConsole(implicit trace: Trace): UIO[TestConsole]
190
191
/**
192
* Access TestConsole service and run workflow
193
*/
194
def testConsoleWith[R, E, A](f: TestConsole => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
195
```
196
197
**Usage Examples:**
198
199
```scala
200
import zio.test._
201
202
test("console input/output") {
203
for {
204
// Feed input for reading
205
_ <- TestConsole.feedLines("Alice", "25")
206
207
// Read input
208
name <- Console.readLine("Enter name: ")
209
age <- Console.readLine("Enter age: ")
210
211
// Write output
212
_ <- Console.printLine(s"Hello $name, you are $age years old")
213
214
// Verify output
215
output <- TestConsole.output
216
} yield assertTrue(
217
name == "Alice" &&
218
age == "25" &&
219
output.contains("Hello Alice, you are 25 years old")
220
)
221
}
222
223
test("error output") {
224
for {
225
// Write to error stream
226
_ <- Console.printLineError("Error occurred")
227
228
// Check error output
229
errors <- TestConsole.errorOutput
230
} yield assertTrue(errors.contains("Error occurred"))
231
}
232
```
233
234
### TestRandom Service
235
236
Controllable random number generator for deterministic testing.
237
238
```scala { .api }
239
/**
240
* Controllable random number generator for deterministic testing
241
*/
242
trait TestRandom extends Random {
243
/** Set the random seed */
244
def setSeed(seed: Long)(implicit trace: Trace): UIO[Unit]
245
246
/** Get the current random seed */
247
def getSeed(implicit trace: Trace): UIO[Long]
248
249
/** Feed specific values to be returned by random operations */
250
def feedInts(ints: Int*)(implicit trace: Trace): UIO[Unit]
251
def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit]
252
def feedDoubles(doubles: Double*)(implicit trace: Trace): UIO[Unit]
253
def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit]
254
def feedBooleans(booleans: Boolean*)(implicit trace: Trace): UIO[Unit]
255
def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit]
256
def feedUUIDs(uuids: java.util.UUID*)(implicit trace: Trace): UIO[Unit]
257
def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit]
258
259
/** Clear all fed values */
260
def clearInts(implicit trace: Trace): UIO[Unit]
261
def clearLongs(implicit trace: Trace): UIO[Unit]
262
def clearDoubles(implicit trace: Trace): UIO[Unit]
263
def clearFloats(implicit trace: Trace): UIO[Unit]
264
def clearBooleans(implicit trace: Trace): UIO[Unit]
265
def clearStrings(implicit trace: Trace): UIO[Unit]
266
def clearUUIDs(implicit trace: Trace): UIO[Unit]
267
def clearBytes(implicit trace: Trace): UIO[Unit]
268
269
/** Save the current random state */
270
def save(implicit trace: Trace): UIO[TestRandom.Data]
271
272
/** Restore random to previous state */
273
def restore(data: TestRandom.Data)(implicit trace: Trace): UIO[Unit]
274
}
275
276
object TestRandom {
277
/** Deterministic TestRandom layer */
278
val deterministic: ZLayer[Any, Nothing, TestRandom]
279
280
/** TestRandom layer with specific seed */
281
def seeded(seed: Long): ZLayer[Any, Nothing, TestRandom]
282
283
/** Data representing random state for save/restore */
284
case class Data(seed: Long, ints: List[Int], /* other fed values */)
285
}
286
287
/**
288
* Access TestRandom service
289
*/
290
def testRandom(implicit trace: Trace): UIO[TestRandom]
291
292
/**
293
* Access TestRandom service and run workflow
294
*/
295
def testRandomWith[R, E, A](f: TestRandom => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
296
```
297
298
**Usage Examples:**
299
300
```scala
301
import zio.test._
302
303
test("deterministic random values") {
304
for {
305
// Feed specific values
306
_ <- TestRandom.feedInts(42, 24, 7)
307
_ <- TestRandom.feedBooleans(true, false, true)
308
309
// Generate values (will use fed values)
310
n1 <- Random.nextInt
311
n2 <- Random.nextInt
312
n3 <- Random.nextInt
313
314
b1 <- Random.nextBoolean
315
b2 <- Random.nextBoolean
316
b3 <- Random.nextBoolean
317
} yield assertTrue(
318
n1 == 42 && n2 == 24 && n3 == 7 &&
319
b1 && !b2 && b3
320
)
321
}
322
323
test("seeded random") {
324
for {
325
// Set specific seed for reproducible tests
326
_ <- TestRandom.setSeed(12345L)
327
328
// Generate values (will be deterministic)
329
values <- ZIO.collectAll(List.fill(5)(Random.nextInt))
330
331
// Reset to same seed
332
_ <- TestRandom.setSeed(12345L)
333
334
// Generate same values again
335
sameValues <- ZIO.collectAll(List.fill(5)(Random.nextInt))
336
} yield assertTrue(values == sameValues)
337
}
338
```
339
340
### TestSystem Service
341
342
Controllable system environment for testing system interactions.
343
344
```scala { .api }
345
/**
346
* Controllable system environment for testing system interactions
347
*/
348
trait TestSystem extends System {
349
/** Set environment variable */
350
def putEnv(name: String, value: String)(implicit trace: Trace): UIO[Unit]
351
352
/** Remove environment variable */
353
def clearEnv(name: String)(implicit trace: Trace): UIO[Unit]
354
355
/** Set system property */
356
def putProperty(name: String, value: String)(implicit trace: Trace): UIO[Unit]
357
358
/** Remove system property */
359
def clearProperty(name: String)(implicit trace: Trace): UIO[Unit]
360
361
/** Set all environment variables */
362
def setEnvs(envs: Map[String, String])(implicit trace: Trace): UIO[Unit]
363
364
/** Set all system properties */
365
def setProperties(props: Map[String, String])(implicit trace: Trace): UIO[Unit]
366
367
/** Save the current system state */
368
def save(implicit trace: Trace): UIO[TestSystem.Data]
369
370
/** Restore system to previous state */
371
def restore(data: TestSystem.Data)(implicit trace: Trace): UIO[Unit]
372
}
373
374
object TestSystem {
375
/** Default TestSystem layer */
376
val default: ZLayer[Any, Nothing, TestSystem]
377
378
/** Data representing system state for save/restore */
379
case class Data(envs: Map[String, String], properties: Map[String, String])
380
}
381
382
/**
383
* Access TestSystem service
384
*/
385
def testSystem(implicit trace: Trace): UIO[TestSystem]
386
387
/**
388
* Access TestSystem service and run workflow
389
*/
390
def testSystemWith[R, E, A](f: TestSystem => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
391
```
392
393
**Usage Examples:**
394
395
```scala
396
import zio.test._
397
398
test("environment variables") {
399
for {
400
// Set environment variable
401
_ <- TestSystem.putEnv("MY_VAR", "test_value")
402
403
// Read environment variable
404
value <- System.env("MY_VAR")
405
406
// Clear environment variable
407
_ <- TestSystem.clearEnv("MY_VAR")
408
409
// Verify it's gone
410
cleared <- System.env("MY_VAR")
411
} yield assertTrue(
412
value.contains("test_value") &&
413
cleared.isEmpty
414
)
415
}
416
417
test("system properties") {
418
for {
419
// Set system property
420
_ <- TestSystem.putProperty("test.prop", "test_value")
421
422
// Read system property
423
value <- System.property("test.prop")
424
425
// Clear system property
426
_ <- TestSystem.clearProperty("test.prop")
427
428
// Verify it's gone
429
cleared <- System.property("test.prop")
430
} yield assertTrue(
431
value.contains("test_value") &&
432
cleared.isEmpty
433
)
434
}
435
```
436
437
### TestConfig Service
438
439
Test configuration service for controlling test execution parameters.
440
441
```scala { .api }
442
/**
443
* Test configuration service
444
*/
445
trait TestConfig {
446
/** Number of samples for property-based tests */
447
def samples: Int
448
449
/** Maximum number of shrinking iterations */
450
def shrinks: Int
451
452
/** Repeats configuration */
453
def repeats: Int
454
455
/** Retries configuration */
456
def retries: Int
457
458
/** Test aspect to apply to property tests */
459
def checkAspect: TestAspectPoly
460
}
461
462
object TestConfig {
463
/** Live TestConfig layer with specified parameters */
464
def live(samples: Int, shrinks: Int, repeats: Int, retries: Int): ZLayer[Any, Nothing, TestConfig]
465
466
/** Default TestConfig layer */
467
val default: ZLayer[Any, Nothing, TestConfig]
468
}
469
470
/**
471
* Access TestConfig service
472
*/
473
def testConfig(implicit trace: Trace): UIO[TestConfig]
474
475
/**
476
* Access TestConfig service and run workflow
477
*/
478
def testConfigWith[R, E, A](f: TestConfig => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
479
```
480
481
### Annotations Service
482
483
Service for managing test annotations and metadata.
484
485
```scala { .api }
486
/**
487
* Service for managing test annotations and metadata
488
*/
489
trait Annotations {
490
/** Get annotation value for the specified key */
491
def get[V](key: TestAnnotation[V])(implicit trace: Trace): UIO[V]
492
493
/** Annotate with key-value pair */
494
def annotate[V](key: TestAnnotation[V], value: V)(implicit trace: Trace): UIO[Unit]
495
496
/** Get all annotations as a map */
497
def annotated(implicit trace: Trace): UIO[TestAnnotationMap]
498
499
/** Run effect with additional annotation */
500
def withAnnotation[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
501
}
502
503
object Annotations {
504
/** Live Annotations layer */
505
val live: ZLayer[Any, Nothing, Annotations]
506
}
507
508
/**
509
* Access Annotations service
510
*/
511
def annotations(implicit trace: Trace): UIO[Annotations]
512
513
/**
514
* Access Annotations service and run workflow
515
*/
516
def annotationsWith[R, E, A](f: Annotations => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
517
```
518
519
### Sized Service
520
521
Service providing size parameter for generators.
522
523
```scala { .api }
524
/**
525
* Service providing size parameter for generators
526
*/
527
trait Sized {
528
/** Get the current size parameter */
529
def size(implicit trace: Trace): UIO[Int]
530
}
531
532
object Sized {
533
/** Live Sized layer with specified size */
534
def live(size: Int): ZLayer[Any, Nothing, Sized]
535
}
536
537
/**
538
* Access Sized service
539
*/
540
def sized(implicit trace: Trace): UIO[Sized]
541
542
/**
543
* Access Sized service and run workflow
544
*/
545
def sizedWith[R, E, A](f: Sized => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
546
```
547
548
### Live Service
549
550
Service for accessing live environment during tests.
551
552
```scala { .api }
553
/**
554
* Service for accessing live environment during tests
555
*/
556
trait Live {
557
/** Run effect with live environment instead of test environment */
558
def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
559
}
560
561
object Live {
562
/** Default Live layer */
563
val default: ZLayer[Any, Nothing, Live]
564
}
565
566
/**
567
* Access Live service
568
*/
569
def live(implicit trace: Trace): UIO[Live]
570
571
/**
572
* Access Live service and run workflow
573
*/
574
def liveWith[R, E, A](f: Live => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
575
576
/**
577
* Run effect with live environment
578
*/
579
def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]
580
581
/**
582
* Transform effect with live environment access
583
*/
584
def withLive[R, E, E1, A, B](zio: ZIO[R, E, A])(f: ZIO[R, E, A] => ZIO[R, E1, B])(implicit trace: Trace): ZIO[R, E1, B]
585
```
586
587
### Service Management Functions
588
589
Functions for managing and customizing test services.
590
591
```scala { .api }
592
/**
593
* Run workflow with custom annotations service
594
*/
595
def withAnnotations[R, E, A <: Annotations, B](annotations: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]
596
597
/**
598
* Set annotations service in scope
599
*/
600
def withAnnotationsScoped[A <: Annotations](annotations: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]
601
602
/**
603
* Run workflow with custom config service
604
*/
605
def withTestConfig[R, E, A <: TestConfig, B](testConfig: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]
606
607
/**
608
* Set config service in scope
609
*/
610
def withTestConfigScoped[A <: TestConfig](testConfig: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]
611
612
/**
613
* Run workflow with custom sized service
614
*/
615
def withSized[R, E, A <: Sized, B](sized: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]
616
617
/**
618
* Set sized service in scope
619
*/
620
def withSizedScoped[A <: Sized](sized: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]
621
622
/**
623
* Run workflow with custom live service
624
*/
625
def withLive[R, E, A <: Live, B](live: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]
626
627
/**
628
* Set live service in scope
629
*/
630
def withLiveScoped[A <: Live](live: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]
631
```
632
633
**Usage Examples:**
634
635
```scala
636
import zio.test._
637
638
test("custom test configuration") {
639
val customConfig = new TestConfig {
640
def samples = 1000 // More samples than default
641
def shrinks = 50 // More shrinking iterations
642
def repeats = 1
643
def retries = 1
644
def checkAspect = TestAspect.identity
645
}
646
647
withTestConfig(customConfig) {
648
check(Gen.int) { n =>
649
assertTrue(n.isInstanceOf[Int])
650
}
651
}
652
}
653
654
test("custom size parameter") {
655
val largeSize = new Sized {
656
def size(implicit trace: Trace) = ZIO.succeed(200)
657
}
658
659
withSized(largeSize) {
660
check(Gen.sized(n => Gen.listOfN(n)(Gen.int))) { numbers =>
661
assertTrue(numbers.size <= 200)
662
}
663
}
664
}
665
```