0
# Test Environment
1
2
Testable versions of ZIO services enabling deterministic testing of time, console, randomness, and system properties. The test environment provides controllable, predictable implementations of environmental services.
3
4
## Capabilities
5
6
### TestClock Service
7
8
Deterministic time control for testing time-dependent operations.
9
10
```scala { .api }
11
/**
12
* TestClock enables deterministic testing of time-dependent effects.
13
* Instead of waiting for real time, effects are scheduled and executed
14
* when the test clock is advanced.
15
*/
16
object TestClock {
17
/**
18
* Advance the test clock by the specified duration
19
* @param duration - Time to advance
20
* @returns Effect that advances time and runs scheduled effects
21
*/
22
def adjust(duration: Duration): URIO[TestClock, Unit]
23
24
/**
25
* Set the test clock to a specific time
26
* @param duration - Absolute time since epoch
27
* @returns Effect that sets time and runs scheduled effects
28
*/
29
def setTime(duration: Duration): URIO[TestClock, Unit]
30
31
/**
32
* Set the test clock to a specific date/time
33
* @param dateTime - Specific date and time
34
* @returns Effect that sets time and runs scheduled effects
35
*/
36
def setDateTime(dateTime: OffsetDateTime): URIO[TestClock, Unit]
37
38
/**
39
* Set the time zone for the test clock
40
* @param zone - Time zone to use
41
* @returns Effect that changes the time zone
42
*/
43
def setTimeZone(zone: ZoneId): URIO[TestClock, Unit]
44
45
/**
46
* Get list of scheduled sleep durations
47
* @returns Current sleep schedule
48
*/
49
val sleeps: ZIO[TestClock, Nothing, List[Duration]]
50
51
/**
52
* Get current time zone
53
* @returns Current time zone setting
54
*/
55
val timeZone: URIO[TestClock, ZoneId]
56
57
/**
58
* Save current clock state for later restoration
59
* @returns Effect that when run will restore the clock state
60
*/
61
val save: ZIO[TestClock, Nothing, UIO[Unit]]
62
}
63
```
64
65
**Usage Examples:**
66
67
```scala
68
import zio.test.environment.TestClock
69
import zio.duration._
70
71
// Test timeout behavior
72
testM("operation times out") {
73
for {
74
fiber <- longRunningOperation.timeout(1.minute).fork
75
_ <- TestClock.adjust(1.minute)
76
result <- fiber.join
77
} yield assert(result)(isNone)
78
}
79
80
// Test recurring operations
81
testM("recurring task") {
82
for {
83
queue <- Queue.unbounded[Unit]
84
_ <- queue.offer(()).delay(30.seconds).forever.fork
85
_ <- TestClock.adjust(30.seconds)
86
item1 <- queue.take
87
_ <- TestClock.adjust(30.seconds)
88
item2 <- queue.take
89
} yield assert((item1, item2))(equalTo(((), ())))
90
}
91
```
92
93
### TestConsole Service
94
95
Console input/output testing with controllable input and output capture.
96
97
```scala { .api }
98
/**
99
* TestConsole provides testable console operations with input feeding
100
* and output capture capabilities.
101
*/
102
object TestConsole {
103
/**
104
* Add lines to the input buffer for console reads
105
* @param lines - Lines to add to input buffer
106
* @returns Effect that feeds the input buffer
107
*/
108
def feedLines(lines: String*): URIO[TestConsole, Unit]
109
110
/**
111
* Get all output written to console
112
* @returns Vector of all output strings
113
*/
114
val output: ZIO[TestConsole, Nothing, Vector[String]]
115
116
/**
117
* Get all error output written to console
118
* @returns Vector of all error output strings
119
*/
120
val outputErr: ZIO[TestConsole, Nothing, Vector[String]]
121
122
/**
123
* Clear the input buffer
124
* @returns Effect that clears input
125
*/
126
val clearInput: URIO[TestConsole, Unit]
127
128
/**
129
* Clear the output buffer
130
* @returns Effect that clears output
131
*/
132
val clearOutput: URIO[TestConsole, Unit]
133
134
/**
135
* Run effect with debug mode (output to real console too)
136
* @param zio - Effect to run with debug output
137
* @returns Effect with debug console output enabled
138
*/
139
def debug[R <: TestConsole, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
140
141
/**
142
* Run effect with silent mode (no real console output)
143
* @param zio - Effect to run without console output
144
* @returns Effect with console output suppressed
145
*/
146
def silent[R <: TestConsole, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]
147
148
/**
149
* Save current console state for later restoration
150
* @returns Effect that when run will restore console state
151
*/
152
val save: ZIO[TestConsole, Nothing, UIO[Unit]]
153
}
154
```
155
156
**Usage Examples:**
157
158
```scala
159
import zio.console._
160
import zio.test.environment.TestConsole
161
162
// Test interactive program
163
testM("interactive greeting") {
164
val program = for {
165
_ <- putStrLn("What's your name?")
166
name <- getStrLn
167
_ <- putStrLn(s"Hello, $name!")
168
} yield ()
169
170
for {
171
_ <- TestConsole.feedLines("Alice")
172
_ <- program
173
output <- TestConsole.output
174
} yield assert(output)(equalTo(Vector("What's your name?\n", "Hello, Alice!\n")))
175
}
176
177
// Test error output
178
testM("error logging") {
179
for {
180
_ <- putStrLnErr("Error occurred")
181
errorOut <- TestConsole.outputErr
182
} yield assert(errorOut)(contains("Error occurred\n"))
183
}
184
```
185
186
### TestRandom Service
187
188
Deterministic random number generation with controllable sequences.
189
190
```scala { .api }
191
/**
192
* TestRandom provides deterministic random number generation.
193
* It operates in two modes: pseudo-random (with seeds) and
194
* buffered (with pre-fed values).
195
*/
196
object TestRandom {
197
// Feeding buffers with predetermined values
198
def feedInts(ints: Int*): URIO[TestRandom, Unit]
199
def feedLongs(longs: Long*): URIO[TestRandom, Unit]
200
def feedDoubles(doubles: Double*): URIO[TestRandom, Unit]
201
def feedFloats(floats: Float*): URIO[TestRandom, Unit]
202
def feedBooleans(booleans: Boolean*): URIO[TestRandom, Unit]
203
def feedChars(chars: Char*): URIO[TestRandom, Unit]
204
def feedStrings(strings: String*): URIO[TestRandom, Unit]
205
def feedBytes(bytes: Chunk[Byte]*): URIO[TestRandom, Unit]
206
def feedUUIDs(UUIDs: UUID*): URIO[TestRandom, Unit]
207
208
// Clearing buffers
209
val clearInts: URIO[TestRandom, Unit]
210
val clearLongs: URIO[TestRandom, Unit]
211
val clearDoubles: URIO[TestRandom, Unit]
212
val clearFloats: URIO[TestRandom, Unit]
213
val clearBooleans: URIO[TestRandom, Unit]
214
val clearChars: URIO[TestRandom, Unit]
215
val clearStrings: URIO[TestRandom, Unit]
216
val clearBytes: URIO[TestRandom, Unit]
217
val clearUUIDs: URIO[TestRandom, Unit]
218
219
/**
220
* Set the random seed for pseudo-random generation
221
* @param seed - Seed value for random number generator
222
* @returns Effect that sets the seed
223
*/
224
def setSeed(seed: Long): URIO[TestRandom, Unit]
225
226
/**
227
* Get the current random seed
228
* @returns Current seed value
229
*/
230
val getSeed: URIO[TestRandom, Long]
231
232
/**
233
* Save current random state for later restoration
234
* @returns Effect that when run will restore random state
235
*/
236
val save: ZIO[TestRandom, Nothing, UIO[Unit]]
237
}
238
```
239
240
**Usage Examples:**
241
242
```scala
243
import zio.random._
244
import zio.test.environment.TestRandom
245
246
// Test with predetermined values
247
testM("dice roll sequence") {
248
for {
249
_ <- TestRandom.feedInts(1, 6, 3, 4)
250
roll1 <- nextIntBounded(6).map(_ + 1)
251
roll2 <- nextIntBounded(6).map(_ + 1)
252
roll3 <- nextIntBounded(6).map(_ + 1)
253
roll4 <- nextIntBounded(6).map(_ + 1)
254
} yield assert((roll1, roll2, roll3, roll4))(equalTo((1, 6, 3, 4)))
255
}
256
257
// Test with deterministic seed
258
testM("reproducible randomness") {
259
for {
260
_ <- TestRandom.setSeed(42L)
261
value1 <- nextInt
262
_ <- TestRandom.setSeed(42L)
263
value2 <- nextInt
264
} yield assert(value1)(equalTo(value2))
265
}
266
```
267
268
### TestSystem Service
269
270
System properties and environment variable testing with controlled mappings.
271
272
```scala { .api }
273
/**
274
* TestSystem provides testable system property and environment variable
275
* access without affecting the real system environment.
276
*/
277
object TestSystem {
278
/**
279
* Set a system property in the test environment
280
* @param name - Property name
281
* @param value - Property value
282
* @returns Effect that sets the property
283
*/
284
def putProperty(name: String, value: String): URIO[TestSystem, Unit]
285
286
/**
287
* Set an environment variable in the test environment
288
* @param name - Variable name
289
* @param value - Variable value
290
* @returns Effect that sets the variable
291
*/
292
def putEnv(name: String, value: String): URIO[TestSystem, Unit]
293
294
/**
295
* Set the line separator for the test environment
296
* @param lineSep - Line separator string
297
* @returns Effect that sets the line separator
298
*/
299
def setLineSeparator(lineSep: String): URIO[TestSystem, Unit]
300
301
/**
302
* Clear a system property from the test environment
303
* @param prop - Property name to clear
304
* @returns Effect that removes the property
305
*/
306
def clearProperty(prop: String): URIO[TestSystem, Unit]
307
308
/**
309
* Clear an environment variable from the test environment
310
* @param variable - Variable name to clear
311
* @returns Effect that removes the variable
312
*/
313
def clearEnv(variable: String): URIO[TestSystem, Unit]
314
315
/**
316
* Save current system state for later restoration
317
* @returns Effect that when run will restore system state
318
*/
319
val save: ZIO[TestSystem, Nothing, UIO[Unit]]
320
}
321
```
322
323
**Usage Examples:**
324
325
```scala
326
import zio.system._
327
import zio.test.environment.TestSystem
328
329
// Test configuration loading
330
testM("configuration from environment") {
331
for {
332
_ <- TestSystem.putEnv("APP_MODE", "test")
333
_ <- TestSystem.putProperty("app.timeout", "30")
334
mode <- env("APP_MODE")
335
timeout <- property("app.timeout")
336
} yield assert(mode)(isSome(equalTo("test"))) &&
337
assert(timeout)(isSome(equalTo("30")))
338
}
339
340
// Test different line separators
341
testM("file writing with line separator") {
342
for {
343
_ <- TestSystem.setLineSeparator("\r\n")
344
lines <- system.lineSeparator
345
} yield assert(lines)(equalTo("\r\n"))
346
}
347
```
348
349
### Live Service
350
351
Access to real environment from within test environment.
352
353
```scala { .api }
354
/**
355
* Live service provides access to the real environment for operations
356
* that need actual system resources (like timing out tests).
357
*/
358
object Live {
359
/**
360
* Run an effect with the live environment instead of test environment
361
* @param zio - Effect that needs live environment
362
* @returns Effect that runs with real environment
363
*/
364
def live[E, A](zio: ZIO[ZEnv, E, A]): ZIO[Live, E, A]
365
366
/**
367
* Transform an effect using live environment while keeping test environment for the effect itself
368
* @param zio - Effect to transform
369
* @param f - Transformation function that needs live environment
370
* @returns Transformed effect
371
*/
372
def withLive[R, E, E1, A, B](
373
zio: ZIO[R, E, A]
374
)(f: IO[E, A] => ZIO[ZEnv, E1, B]): ZIO[R with Live, E1, B]
375
}
376
```
377
378
**Usage Examples:**
379
380
```scala
381
import zio.test.environment.Live
382
import zio.clock._
383
384
// Get real time while using test environment
385
testM("real time logging") {
386
for {
387
realTime <- Live.live(currentTime(TimeUnit.MILLISECONDS))
388
_ <- logMessage(s"Test started at $realTime")
389
} yield assertCompletes
390
}
391
392
// Timeout using real time
393
testM("real timeout") {
394
Live.withLive(longRunningTest)(_.timeout(5.seconds))
395
}
396
```
397
398
### Environment Composition
399
400
Combining test services into complete test environments.
401
402
```scala { .api }
403
/**
404
* Combined test environment with all test services
405
*/
406
type TestEnvironment = TestClock with TestConsole with TestRandom with TestSystem with Annotations with Live
407
408
/**
409
* Type alias for ZIO test environment
410
*/
411
type ZTestEnv = TestClock with TestConsole with TestRandom with TestSystem
412
413
/**
414
* Layer that provides complete test environment
415
*/
416
val testEnvironment: Layer[Nothing, TestEnvironment]
417
418
/**
419
* Layer that provides live environment
420
*/
421
val liveEnvironment: Layer[Nothing, ZEnv]
422
```
423
424
**Usage Examples:**
425
426
```scala
427
// Using the complete test environment
428
object MySpec extends DefaultRunnableSpec {
429
def spec = suite("my tests")(
430
testM("uses all test services") {
431
for {
432
_ <- TestClock.adjust(1.hour)
433
_ <- TestConsole.feedLines("input")
434
_ <- TestRandom.feedInts(42)
435
_ <- TestSystem.putEnv("MODE", "test")
436
// Test logic using all services
437
} yield assertCompletes
438
}
439
)
440
}
441
442
// Custom environment layer
443
val myTestEnv = testEnvironment >>> myCustomServices
444
445
myProgram.provideLayer(myTestEnv)
446
```
447
448
## Types
449
450
### Service Types
451
452
```scala { .api }
453
// Individual test services
454
type TestClock = Has[TestClock.Service]
455
type TestConsole = Has[TestConsole.Service]
456
type TestRandom = Has[TestRandom.Service]
457
type TestSystem = Has[TestSystem.Service]
458
type Live = Has[Live.Service]
459
460
// Service data structures
461
object TestClock {
462
final case class Data(
463
duration: Duration,
464
sleeps: List[(Duration, Promise[Nothing, Unit])],
465
timeZone: ZoneId
466
)
467
}
468
469
object TestConsole {
470
final case class Data(
471
input: List[String] = List.empty,
472
output: Vector[String] = Vector.empty,
473
errOutput: Vector[String] = Vector.empty
474
)
475
}
476
477
object TestRandom {
478
final case class Data(
479
seed1: Int,
480
seed2: Int,
481
nextNextGaussians: Queue[Double] = Queue.empty
482
)
483
484
final case class Buffer(
485
booleans: List[Boolean] = List.empty,
486
bytes: List[Chunk[Byte]] = List.empty,
487
chars: List[Char] = List.empty,
488
doubles: List[Double] = List.empty,
489
floats: List[Float] = List.empty,
490
integers: List[Int] = List.empty,
491
longs: List[Long] = List.empty,
492
strings: List[String] = List.empty,
493
UUIDs: List[UUID] = List.empty
494
)
495
}
496
497
object TestSystem {
498
final case class Data(
499
properties: Map[String, String] = Map.empty,
500
envs: Map[String, String] = Map.empty,
501
lineSeparator: String = "\n"
502
)
503
}
504
```
505
506
### Restorable Trait
507
508
```scala { .api }
509
/**
510
* Trait for services that can save and restore their state
511
*/
512
trait Restorable {
513
/**
514
* Save current state and return effect that restores it
515
*/
516
val save: UIO[UIO[Unit]]
517
}
518
```