0
# Property-Based Testing
1
2
Generator system for creating random test data and property-based testing with automatic shrinking support. Property-based testing validates code against general properties rather than specific examples.
3
4
## Capabilities
5
6
### Core Generator Type
7
8
The fundamental generator type for producing test data.
9
10
```scala { .api }
11
/**
12
* A generator of values of type A requiring environment R
13
* @tparam R - Environment type required for generation
14
* @tparam A - Type of values to generate
15
*/
16
case class Gen[-R, +A](sample: ZStream[R, Nothing, Sample[R, A]]) {
17
/** Transform generated values */
18
def map[B](f: A => B)(implicit trace: Trace): Gen[R, B]
19
20
/** Effectfully transform generated values */
21
def mapZIO[R1 <: R, B](f: A => ZIO[R1, Nothing, B])(implicit trace: Trace): Gen[R1, B]
22
23
/** Monadic composition of generators */
24
def flatMap[R1 <: R, B](f: A => Gen[R1, B])(implicit trace: Trace): Gen[R1, B]
25
26
/** Filter generated values by predicate */
27
def filter(f: A => Boolean)(implicit trace: Trace): Gen[R, A]
28
29
/** Effectfully filter generated values */
30
def filterZIO[R1 <: R](f: A => ZIO[R1, Nothing, Boolean])(implicit trace: Trace): Gen[R1, A]
31
32
/** Collect values using partial function */
33
def collect[B](pf: PartialFunction[A, B])(implicit trace: Trace): Gen[R, B]
34
35
/** Combine two generators */
36
def zip[R1 <: R, B](that: Gen[R1, B])(implicit trace: Trace): Gen[R1, (A, B)]
37
38
/** Combine generators with custom function */
39
def zipWith[R1 <: R, B, C](that: Gen[R1, B])(f: (A, B) => C)(implicit trace: Trace): Gen[R1, C]
40
41
/** Concatenate with another generator */
42
def concat[R1 <: R, A1 >: A](that: Gen[R1, A1])(implicit trace: Trace): Gen[R1, A1]
43
44
/** Disable shrinking for this generator */
45
def noShrink(implicit trace: Trace): Gen[R, A]
46
47
/** Replace shrinking strategy */
48
def reshrink[R1 <: R](f: A => ZStream[R1, Nothing, A])(implicit trace: Trace): Gen[R1, A]
49
50
/** Set size parameter for generator */
51
def resize(size: Int)(implicit trace: Trace): Gen[R, A]
52
}
53
```
54
55
### Sample Type
56
57
Container for generated values with shrinking information.
58
59
```scala { .api }
60
/**
61
* A sample contains a generated value and shrinking information
62
* @tparam R - Environment type
63
* @tparam A - Value type
64
*/
65
case class Sample[-R, +A](value: A, shrink: ZStream[R, Nothing, Sample[R, A]]) {
66
/** Transform the sample value */
67
def map[B](f: A => B): Sample[R, B]
68
69
/** Apply function to each element in shrink tree */
70
def foreach[R1 <: R, B](f: A => ZIO[R1, Nothing, B])(implicit trace: Trace): ZIO[R1, Nothing, Sample[R1, B]]
71
72
/** Search through shrinks for values matching predicate */
73
def shrinkSearch(f: A => Boolean)(implicit trace: Trace): ZStream[R, Nothing, A]
74
}
75
```
76
77
### Property Testing Functions
78
79
Core functions for running property-based tests.
80
81
```scala { .api }
82
/**
83
* Check property with sufficient number of samples
84
* @param rv - Generator producing test values
85
* @param test - Property test function
86
* @return ZIO effect producing TestResult
87
*/
88
def check[R, A, In](rv: Gen[R, A])(test: A => In)(implicit
89
checkConstructor: CheckConstructor[R, In],
90
sourceLocation: SourceLocation,
91
trace: Trace
92
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
93
94
/**
95
* Check property for all values from finite generator
96
* @param rv - Finite generator producing test values
97
* @param test - Property test function
98
* @return ZIO effect producing TestResult
99
*/
100
def checkAll[R, A, In](rv: Gen[R, A])(test: A => In)(implicit
101
checkConstructor: CheckConstructor[R, In],
102
sourceLocation: SourceLocation,
103
trace: Trace
104
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
105
106
/**
107
* Check property with specific number of samples
108
* @param n - Number of samples to test
109
* @return CheckN instance for applying to generators
110
*/
111
def checkN(n: Int): CheckVariants.CheckN
112
113
/**
114
* Check property in parallel with sufficient samples
115
* @param rv - Generator producing test values
116
* @param parallelism - Number of parallel workers
117
* @param test - Property test function
118
* @return ZIO effect producing TestResult
119
*/
120
def checkPar[R, A, In](rv: Gen[R, A], parallelism: Int)(test: A => In)(implicit
121
checkConstructor: CheckConstructor[R, In],
122
sourceLocation: SourceLocation,
123
trace: Trace
124
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
125
126
/**
127
* Check all values from finite generator in parallel
128
* @param rv - Finite generator producing test values
129
* @param parallelism - Number of parallel workers
130
* @param test - Property test function
131
* @return ZIO effect producing TestResult
132
*/
133
def checkAllPar[R, A, In](rv: Gen[R, A], parallelism: Int)(test: A => In)(implicit
134
checkConstructor: CheckConstructor[R, In],
135
sourceLocation: SourceLocation,
136
trace: Trace
137
): ZIO[checkConstructor.OutEnvironment, checkConstructor.OutError, TestResult]
138
```
139
140
**Usage Examples:**
141
142
```scala
143
import zio.test._
144
import zio.test.Gen._
145
146
test("property-based testing") {
147
// Simple property test
148
check(int) { n =>
149
assertTrue((n + 1) > n)
150
} &&
151
152
// Test with multiple generators
153
check(int, string) { (n, s) =>
154
assertTrue(s.length >= 0 && n.toString.nonEmpty)
155
} &&
156
157
// Test with specific number of samples
158
checkN(1000)(double) { d =>
159
assertTrue(!d.isNaN || d.isNaN) // Always true
160
} &&
161
162
// Test all values from finite generator
163
checkAll(oneOf(1, 2, 3, 4, 5)) { n =>
164
assertTrue(n >= 1 && n <= 5)
165
}
166
}
167
```
168
169
### Primitive Generators
170
171
Generators for basic Scala types.
172
173
```scala { .api }
174
/** Boolean generator */
175
def boolean: Gen[Any, Boolean]
176
177
/** Byte generator with full range */
178
def byte: Gen[Any, Byte]
179
180
/** Byte generator within specified bounds */
181
def byteWith(min: Byte, max: Byte): Gen[Any, Byte]
182
183
/** Short generator with full range */
184
def short: Gen[Any, Short]
185
186
/** Short generator within specified bounds */
187
def shortWith(min: Short, max: Short): Gen[Any, Short]
188
189
/** Int generator with full range */
190
def int: Gen[Any, Int]
191
192
/** Int generator within specified bounds */
193
def intWith(min: Int, max: Int): Gen[Any, Int]
194
195
/** Long generator with full range */
196
def long: Gen[Any, Long]
197
198
/** Long generator within specified bounds */
199
def longWith(min: Long, max: Long): Gen[Any, Long]
200
201
/** Float generator */
202
def float: Gen[Any, Float]
203
204
/** Float generator within specified bounds */
205
def floatWith(min: Float, max: Float): Gen[Any, Float]
206
207
/** Double generator */
208
def double: Gen[Any, Double]
209
210
/** Double generator within specified bounds */
211
def doubleWith(min: Double, max: Double): Gen[Any, Double]
212
213
/** BigInt generator */
214
def bigInt: Gen[Any, BigInt]
215
216
/** BigInt generator within specified bounds */
217
def bigIntWith(min: BigInt, max: BigInt): Gen[Any, BigInt]
218
219
/** BigDecimal generator */
220
def bigDecimal: Gen[Any, BigDecimal]
221
222
/** BigDecimal generator within specified bounds */
223
def bigDecimalWith(min: BigDecimal, max: BigDecimal): Gen[Any, BigDecimal]
224
225
/** Java BigInteger generator */
226
def bigIntegerJava: Gen[Any, java.math.BigInteger]
227
228
/** Java BigDecimal generator */
229
def bigDecimalJava: Gen[Any, java.math.BigDecimal]
230
```
231
232
**Usage Examples:**
233
234
```scala
235
import zio.test._
236
import zio.test.Gen._
237
238
test("primitive generators") {
239
check(boolean) { b =>
240
assertTrue(b == true || b == false)
241
} &&
242
243
check(intWith(1, 100)) { n =>
244
assertTrue(n >= 1 && n <= 100)
245
} &&
246
247
check(doubleWith(0.0, 1.0)) { d =>
248
assertTrue(d >= 0.0 && d <= 1.0)
249
}
250
}
251
```
252
253
### Character and String Generators
254
255
Generators for characters and strings with various constraints.
256
257
```scala { .api }
258
/** Character generator with full Unicode range */
259
def char: Gen[Any, Char]
260
261
/** Character generator within specified bounds */
262
def charWith(min: Char, max: Char): Gen[Any, Char]
263
264
/** Alphabetic character generator (a-z, A-Z) */
265
def alphaChar: Gen[Any, Char]
266
267
/** Alphanumeric character generator (a-z, A-Z, 0-9) */
268
def alphaNumericChar: Gen[Any, Char]
269
270
/** Numeric character generator (0-9) */
271
def numericChar: Gen[Any, Char]
272
273
/** ASCII character generator (0-127) */
274
def asciiChar: Gen[Any, Char]
275
276
/** Printable ASCII character generator */
277
def printableChar: Gen[Any, Char]
278
279
/** Hexadecimal character generator (0-9, a-f, A-F) */
280
def hexChar: Gen[Any, Char]
281
282
/** Lowercase hexadecimal character generator (0-9, a-f) */
283
def hexCharLower: Gen[Any, Char]
284
285
/** Uppercase hexadecimal character generator (0-9, A-F) */
286
def hexCharUpper: Gen[Any, Char]
287
288
/** Unicode character generator */
289
def unicodeChar: Gen[Any, Char]
290
291
/** Whitespace character generator */
292
def whitespaceChars: Gen[Any, Char]
293
294
/** String generator using printable characters */
295
def string: Gen[Any, String]
296
297
/** Non-empty string generator */
298
def string1: Gen[Any, String]
299
300
/** Fixed-length string generator */
301
def stringN(n: Int): Gen[Any, String]
302
303
/** Bounded-length string generator */
304
def stringBounded(min: Int, max: Int): Gen[Any, String]
305
306
/** Alphanumeric string generator */
307
def alphaNumericString: Gen[Any, String]
308
309
/** Bounded alphanumeric string generator */
310
def alphaNumericStringBounded(min: Int, max: Int): Gen[Any, String]
311
312
/** ASCII string generator */
313
def asciiString: Gen[Any, String]
314
315
/** ISO-8859-1 string generator */
316
def iso_8859_1: Gen[Any, String]
317
```
318
319
**Usage Examples:**
320
321
```scala
322
import zio.test._
323
import zio.test.Gen._
324
325
test("character and string generators") {
326
check(alphaChar) { c =>
327
assertTrue(c.isLetter)
328
} &&
329
330
check(alphaNumericString) { s =>
331
assertTrue(s.forall(c => c.isLetterOrDigit))
332
} &&
333
334
check(stringBounded(5, 10)) { s =>
335
assertTrue(s.length >= 5 && s.length <= 10)
336
}
337
}
338
```
339
340
### Collection Generators
341
342
Generators for various collection types.
343
344
```scala { .api }
345
/** List generator using another generator */
346
def listOf[A](gen: Gen[Any, A]): Gen[Any, List[A]]
347
348
/** Non-empty list generator */
349
def listOf1[A](gen: Gen[Any, A]): Gen[Any, List[A]]
350
351
/** Fixed-size list generator */
352
def listOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, List[A]]
353
354
/** Bounded-size list generator */
355
def listOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, List[A]]
356
357
/** Vector generator using another generator */
358
def vectorOf[A](gen: Gen[Any, A]): Gen[Any, Vector[A]]
359
360
/** Non-empty vector generator */
361
def vectorOf1[A](gen: Gen[Any, A]): Gen[Any, Vector[A]]
362
363
/** Fixed-size vector generator */
364
def vectorOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Vector[A]]
365
366
/** Bounded-size vector generator */
367
def vectorOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Vector[A]]
368
369
/** Chunk generator using another generator */
370
def chunkOf[A](gen: Gen[Any, A]): Gen[Any, Chunk[A]]
371
372
/** Non-empty chunk generator */
373
def chunkOf1[A](gen: Gen[Any, A]): Gen[Any, Chunk[A]]
374
375
/** Fixed-size chunk generator */
376
def chunkOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Chunk[A]]
377
378
/** Bounded-size chunk generator */
379
def chunkOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Chunk[A]]
380
381
/** Set generator using another generator */
382
def setOf[A](gen: Gen[Any, A]): Gen[Any, Set[A]]
383
384
/** Non-empty set generator */
385
def setOf1[A](gen: Gen[Any, A]): Gen[Any, Set[A]]
386
387
/** Fixed-size set generator */
388
def setOfN[A](n: Int)(gen: Gen[Any, A]): Gen[Any, Set[A]]
389
390
/** Bounded-size set generator */
391
def setOfBounded[A](min: Int, max: Int)(gen: Gen[Any, A]): Gen[Any, Set[A]]
392
393
/** Map generator using key and value generators */
394
def mapOf[K, V](keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
395
396
/** Non-empty map generator */
397
def mapOf1[K, V](keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
398
399
/** Fixed-size map generator */
400
def mapOfN[K, V](n: Int)(keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
401
402
/** Bounded-size map generator */
403
def mapOfBounded[K, V](min: Int, max: Int)(keyGen: Gen[Any, K], valueGen: Gen[Any, V]): Gen[Any, Map[K, V]]
404
```
405
406
**Usage Examples:**
407
408
```scala
409
import zio.test._
410
import zio.test.Gen._
411
412
test("collection generators") {
413
check(listOf(int)) { numbers =>
414
assertTrue(numbers.forall(_.isInstanceOf[Int]))
415
} &&
416
417
check(listOfBounded(1, 5)(string)) { strings =>
418
assertTrue(strings.size >= 1 && strings.size <= 5)
419
} &&
420
421
check(setOf(intWith(1, 10))) { numbers =>
422
assertTrue(numbers.forall(n => n >= 1 && n <= 10))
423
} &&
424
425
check(mapOf(string, int)) { map =>
426
assertTrue(map.keys.forall(_.isInstanceOf[String]) &&
427
map.values.forall(_.isInstanceOf[Int]))
428
}
429
}
430
```
431
432
### Option and Either Generators
433
434
Generators for Option and Either types.
435
436
```scala { .api }
437
/** Option generator that may produce Some or None */
438
def option[A](gen: Gen[Any, A]): Gen[Any, Option[A]]
439
440
/** Some generator that always produces Some */
441
def some[A](gen: Gen[Any, A]): Gen[Any, Some[A]]
442
443
/** None generator that always produces None */
444
def none: Gen[Any, None.type]
445
446
/** Either generator using left and right generators */
447
def either[A, B](leftGen: Gen[Any, A], rightGen: Gen[Any, B]): Gen[Any, Either[A, B]]
448
```
449
450
### Special Utility Generators
451
452
Generators for specific use cases and combinations.
453
454
```scala { .api }
455
/** Constant value generator */
456
def const[A](value: A): Gen[Any, A]
457
458
/** Constant sample generator with shrinking */
459
def constSample[R, A](sample: Sample[R, A]): Gen[R, A]
460
461
/** Generator from specific elements */
462
def elements[A](as: A*): Gen[Any, A]
463
464
/** Generator choosing from multiple generators */
465
def oneOf[A](gens: Gen[Any, A]*): Gen[Any, A]
466
467
/** Weighted generator selection */
468
def weighted[A](weightedGens: (Gen[Any, A], Double)*): Gen[Any, A]
469
470
/** Generator from iterable collection */
471
def fromIterable[A](as: Iterable[A]): Gen[Any, A]
472
473
/** Generator from ZIO effect */
474
def fromZIO[R, A](zio: ZIO[R, Nothing, A]): Gen[R, A]
475
476
/** Generator from ZIO sample effect */
477
def fromZIOSample[R, A](zio: ZIO[R, Nothing, Sample[R, A]]): Gen[R, A]
478
479
/** Lazy generator evaluation */
480
def suspend[R, A](gen: => Gen[R, A]): Gen[R, A]
481
482
/** Empty generator (produces no values) */
483
def empty[A]: Gen[Any, A]
484
485
/** Unit generator */
486
def unit: Gen[Any, Unit]
487
488
/** UUID generator */
489
def uuid: Gen[Any, java.util.UUID]
490
491
/** Currency generator */
492
def currency: Gen[Any, java.util.Currency]
493
494
/** Throwable generator */
495
def throwable: Gen[Any, Throwable]
496
```
497
498
**Usage Examples:**
499
500
```scala
501
import zio.test._
502
import zio.test.Gen._
503
504
test("special generators") {
505
check(const(42)) { n =>
506
assertTrue(n == 42)
507
} &&
508
509
check(elements("red", "green", "blue")) { color =>
510
assertTrue(Set("red", "green", "blue").contains(color))
511
} &&
512
513
check(oneOf(int, string.map(_.length))) { value =>
514
assertTrue(value.isInstanceOf[Int])
515
} &&
516
517
check(option(string)) { maybeString =>
518
assertTrue(maybeString.isEmpty || maybeString.get.isInstanceOf[String])
519
}
520
}
521
```
522
523
### Size-Related Generators
524
525
Generators that work with size parameters.
526
527
```scala { .api }
528
/** Current size generator */
529
def size: Gen[Sized, Int]
530
531
/** Size-dependent generator */
532
def sized[R, A](f: Int => Gen[R, A]): Gen[R with Sized, A]
533
534
/** Small-sized generator (size / 4) */
535
def small[R, A](gen: Gen[R, A]): Gen[R, A]
536
537
/** Medium-sized generator (size / 2) */
538
def medium[R, A](gen: Gen[R, A]): Gen[R, A]
539
540
/** Large-sized generator (size * 2) */
541
def large[R, A](gen: Gen[R, A]): Gen[R, A]
542
```
543
544
### Mathematical Distribution Generators
545
546
Generators for specific mathematical distributions.
547
548
```scala { .api }
549
/** Uniform distribution generator */
550
def uniform[A: Numeric](min: A, max: A): Gen[Any, A]
551
552
/** Exponential distribution generator */
553
def exponential[A: Numeric](lambda: A): Gen[Any, A]
554
```
555
556
### Combinator Functions
557
558
Functions for combining and transforming generators.
559
560
```scala { .api }
561
/** Collect all generator values into a collection */
562
def collectAll[R, A](gens: Iterable[Gen[R, A]]): Gen[R, List[A]]
563
564
/** Concatenate multiple generators */
565
def concatAll[R, A](gens: Iterable[Gen[R, A]]): Gen[R, A]
566
```
567
568
**Usage Examples:**
569
570
```scala
571
import zio.test._
572
import zio.test.Gen._
573
574
test("size and distribution generators") {
575
check(sized(n => listOfN(n)(int))) { numbers =>
576
assertTrue(numbers.size >= 0)
577
} &&
578
579
check(uniform(1.0, 10.0)) { d =>
580
assertTrue(d >= 1.0 && d <= 10.0)
581
} &&
582
583
check(small(listOf(int))) { numbers =>
584
assertTrue(numbers.size <= 25) // Typically smaller due to size reduction
585
}
586
}
587
```