0
# Property Testing
1
2
Comprehensive property-based testing with generators, automatic shrinking, and configurable test execution.
3
4
## Capabilities
5
6
### Property Test Execution Functions
7
8
Functions for running property-based tests with different execution strategies.
9
10
```scala { .api }
11
/**
12
* Tests a property with random samples from generator
13
* @param rv - Generator for test values
14
* @param test - Property test function
15
* @returns Test result aggregating all sample results
16
*/
17
def check[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]
18
19
/**
20
* Tests an effectful property with random samples
21
* @param rv - Generator for test values
22
* @param test - Effectful property test function
23
* @returns ZIO effect producing aggregated test result
24
*/
25
def checkM[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]
26
27
/**
28
* Tests a property exhaustively with all samples from generator
29
* @param rv - Generator for test values (should be finite)
30
* @param test - Property test function
31
* @returns Test result for all generated values
32
*/
33
def checkAll[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]
34
35
/**
36
* Tests an effectful property exhaustively
37
* @param rv - Generator for test values (should be finite)
38
* @param test - Effectful property test function
39
* @returns ZIO effect producing complete test result
40
*/
41
def checkAllM[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]
42
43
/**
44
* Tests a property with specific number of samples
45
* @param n - Number of samples to generate
46
* @returns CheckN instance for chaining with generator and test
47
*/
48
def checkN(n: Int): CheckVariants.CheckN
49
50
/**
51
* Tests an effectful property with specific number of samples
52
* @param n - Number of samples to generate
53
* @returns CheckNM instance for chaining with generator and test
54
*/
55
def checkNM(n: Int): CheckVariants.CheckNM
56
```
57
58
### Gen Class and Core Operations
59
60
The main generator class with monadic operations and transformations.
61
62
```scala { .api }
63
/**
64
* A generator that produces values of type A in environment R
65
* @tparam R - Environment requirements (usually TestConfig or Sized)
66
* @tparam A - Type of generated values
67
*/
68
abstract class Gen[-R, +A] {
69
/** Transform generated values with a function */
70
def map[B](f: A => B): Gen[R, B]
71
72
/** Monadic composition of generators */
73
def flatMap[R1 <: R, B](f: A => Gen[R1, B]): Gen[R1, B]
74
75
/** Filter generated values based on predicate */
76
def filter(f: A => Boolean): Gen[R, A]
77
78
/** Enable for-comprehension syntax */
79
def withFilter(f: A => Boolean): Gen[R, A]
80
81
/** Combine with another generator producing tuples */
82
def zip[R1 <: R, B](that: Gen[R1, B]): Gen[R1, (A, B)]
83
84
/** Combine generators with transformation function */
85
def zipWith[R1 <: R, B, C](that: Gen[R1, B])(f: (A, B) => C): Gen[R1, C]
86
87
/** Get stream of samples for testing */
88
def sample: ZStream[R, Nothing, Sample[R, A]]
89
90
/** Disable shrinking for this generator */
91
def noShrink: Gen[R, A]
92
93
/** Make generator produce optional values */
94
def optional: Gen[R, Option[A]]
95
96
/** Set specific size for generation */
97
def resize(size: Int): Gen[R, A]
98
99
/** Set size using an effect */
100
def resizeM[R1 <: R](size: ZIO[R1, Nothing, Int]): Gen[R1, A]
101
}
102
```
103
104
### Basic Value Generators
105
106
Fundamental generators for creating constant and computed values.
107
108
```scala { .api }
109
/** Generator that always produces unit value */
110
val unit: Gen[Any, Unit]
111
112
/** Generator that produces constant value */
113
def const[A](a: => A): Gen[Any, A]
114
115
/** Generator that produces pure value */
116
def succeed[A](a: A): Gen[Any, A]
117
118
/** Generator that produces lazy value */
119
def succeedLazy[A](a: => A): Gen[Any, A]
120
121
/** Generator that produces failure */
122
def fail[E](error: E): Gen[Any, E]
123
124
/** Generator that throws exception */
125
def failingWith[A](gen: Gen[Any, Throwable]): Gen[Any, A]
126
```
127
128
### Primitive Type Generators
129
130
Built-in generators for primitive Scala types.
131
132
```scala { .api }
133
/** Boolean generator */
134
val boolean: Gen[Any, Boolean]
135
136
/** Byte generator with size-based range */
137
val byte: Gen[Sized, Byte]
138
139
/** Short generator with size-based range */
140
val short: Gen[Sized, Short]
141
142
/** Int generator with size-based range */
143
val int: Gen[Sized, Int]
144
145
/** Long generator with size-based range */
146
val long: Gen[Sized, Long]
147
148
/** Float generator */
149
val float: Gen[Sized, Float]
150
151
/** Double generator */
152
val double: Gen[Sized, Double]
153
154
/** BigInt generator */
155
val bigInt: Gen[Sized, BigInt]
156
157
/** BigDecimal generator */
158
val bigDecimal: Gen[Sized, BigDecimal]
159
160
/** Character generator */
161
val char: Gen[Any, Char]
162
163
/** String generator with size-based length */
164
val string: Gen[Sized, String]
165
```
166
167
### Range-based Generators
168
169
Generators that produce values within specified ranges.
170
171
```scala { .api }
172
/** Int generator within range */
173
def int(min: Int, max: Int): Gen[Any, Int]
174
175
/** Long generator within range */
176
def long(min: Long, max: Long): Gen[Any, Long]
177
178
/** Double generator within range */
179
def double(min: Double, max: Double): Gen[Any, Double]
180
181
/** Float generator within range */
182
def float(min: Float, max: Float): Gen[Any, Float]
183
184
/** BigInt generator within range */
185
def bigInt(min: BigInt, max: BigInt): Gen[Any, BigInt]
186
187
/** BigDecimal generator within range */
188
def bigDecimal(min: BigDecimal, max: BigDecimal): Gen[Any, BigDecimal]
189
190
/** Character generator within range */
191
def char(min: Char, max: Char): Gen[Any, Char]
192
```
193
194
### Character Generators
195
196
Specialized generators for different character sets.
197
198
```scala { .api }
199
/** Alphabetic characters (a-z, A-Z) */
200
val alphaChar: Gen[Any, Char]
201
202
/** Alphanumeric characters (a-z, A-Z, 0-9) */
203
val alphaNumericChar: Gen[Any, Char]
204
205
/** Lowercase alphabetic characters (a-z) */
206
val alphaLowerChar: Gen[Any, Char]
207
208
/** Uppercase alphabetic characters (A-Z) */
209
val alphaUpperChar: Gen[Any, Char]
210
211
/** ASCII characters */
212
val asciiChar: Gen[Any, Char]
213
214
/** Hexadecimal characters (0-9, a-f, A-F) */
215
val hexChar: Gen[Any, Char]
216
217
/** Numeric characters (0-9) */
218
val numericChar: Gen[Any, Char]
219
220
/** Printable characters */
221
val printableChar: Gen[Any, Char]
222
```
223
224
### String Generators
225
226
Generators for strings with various constraints.
227
228
```scala { .api }
229
/** Non-empty string generator using character generator */
230
def string1(charGen: Gen[Any, Char]): Gen[Sized, String]
231
232
/** String generator using specific character generator */
233
def stringGen(charGen: Gen[Any, Char]): Gen[Sized, String]
234
235
/** String of fixed length */
236
def stringN(n: Int): Gen[Any, String]
237
238
/** String within length bounds */
239
def stringBounded(min: Int, max: Int): Gen[Any, String]
240
```
241
242
### Collection Generators
243
244
Generators that produce collections of values.
245
246
```scala { .api }
247
/** List generator with size-based length */
248
def listOf[R, A](gen: Gen[R, A]): Gen[R with Sized, List[A]]
249
250
/** Non-empty list generator */
251
def listOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, ::[A]]
252
253
/** List generator with fixed size */
254
def listOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, List[A]]
255
256
/** List generator with bounded size */
257
def listOfBounded[R, A](min: Int, max: Int)(gen: Gen[R, A]): Gen[R, List[A]]
258
259
/** Vector generator with size-based length */
260
def vectorOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]
261
262
/** Non-empty vector generator */
263
def vectorOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]
264
265
/** Vector generator with fixed size */
266
def vectorOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Vector[A]]
267
268
/** Set generator with size-based cardinality */
269
def setOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]
270
271
/** Non-empty set generator */
272
def setOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]
273
274
/** Set generator with fixed size */
275
def setOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Set[A]]
276
277
/** Map generator with size-based cardinality */
278
def mapOf[R, A, B](keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R with Sized, Map[A, B]]
279
280
/** Non-empty map generator */
281
def mapOf1[R, A, B](keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R with Sized, Map[A, B]]
282
283
/** Map generator with fixed size */
284
def mapOfN[R, A, B](n: Int)(keyGen: Gen[R, A], valueGen: Gen[R, B]): Gen[R, Map[A, B]]
285
```
286
287
### Choice and Option Generators
288
289
Generators for selecting from alternatives and creating optional values.
290
291
```scala { .api }
292
/** Generator that chooses from multiple generators */
293
def oneOf[R, A](gen: Gen[R, A], gens: Gen[R, A]*): Gen[R, A]
294
295
/** Generator that chooses from values */
296
def elements[A](a: A, as: A*): Gen[Any, A]
297
298
/** Generator that chooses from collection */
299
def fromIterable[A](as: Iterable[A]): Gen[Any, A]
300
301
/** Weighted choice generator */
302
def weighted[R, A](gs: (Gen[R, A], Double)*): Gen[R, A]
303
304
/** None generator */
305
val none: Gen[Any, Option[Nothing]]
306
307
/** Some generator from another generator */
308
def some[R, A](gen: Gen[R, A]): Gen[R, Option[A]]
309
310
/** Optional generator (Some or None) */
311
def option[R, A](gen: Gen[R, A]): Gen[R, Option[A]]
312
```
313
314
### Either Generators
315
316
Generators for Either values.
317
318
```scala { .api }
319
/** Either generator from left and right generators */
320
def either[R, A, B](left: Gen[R, A], right: Gen[R, B]): Gen[R, Either[A, B]]
321
322
/** Left generator */
323
def left[R, A](gen: Gen[R, A]): Gen[R, Either[A, Nothing]]
324
325
/** Right generator */
326
def right[R, A](gen: Gen[R, A]): Gen[R, Either[Nothing, A]]
327
```
328
329
### Sample Class
330
331
Represents generated samples with shrinking capabilities.
332
333
```scala { .api }
334
/**
335
* A generated sample that can be shrunk for failure minimization
336
* @tparam R - Environment requirements
337
* @tparam A - Type of sample value
338
*/
339
case class Sample[-R, +A](value: A, shrink: ZStream[R, Nothing, Sample[R, A]]) {
340
/** Transform sample value */
341
def map[B](f: A => B): Sample[R, B]
342
343
/** Monadic composition */
344
def flatMap[R1 <: R, B](f: A => Sample[R1, B]): Sample[R1, B]
345
346
/** Filter sample */
347
def filter(f: A => Boolean): Sample[R, A]
348
349
/** Transform with effect */
350
def foreach[R1 <: R, B](f: A => ZIO[R1, Nothing, B]): ZIO[R1, Nothing, Sample[R1, B]]
351
352
/** Search for minimal failing sample */
353
def shrinkSearch(f: A => Boolean): ZStream[R, Nothing, A]
354
}
355
```
356
357
## Usage Examples
358
359
```scala
360
import zio.test._
361
import zio.test.Gen._
362
363
// Basic property test
364
test("string length property") {
365
check(string) { s =>
366
assert(s.reverse.reverse)(equalTo(s))
367
}
368
}
369
370
// Multiple generators
371
test("addition property") {
372
check(int, int) { (a, b) =>
373
assert(a + b)(equalTo(b + a))
374
}
375
}
376
377
// Custom generator
378
val evenInt = int.filter(_ % 2 == 0)
379
380
test("even numbers") {
381
check(evenInt) { n =>
382
assert(n % 2)(equalTo(0))
383
}
384
}
385
386
// Collection properties
387
test("list concatenation") {
388
check(listOf(int), listOf(int)) { (xs, ys) =>
389
assert((xs ++ ys).length)(equalTo(xs.length + ys.length))
390
}
391
}
392
393
// Effectful property test
394
testM("async property") {
395
checkM(int) { n =>
396
for {
397
result <- someAsyncOperation(n)
398
} yield assert(result)(isGreaterThan(n))
399
}
400
}
401
402
// Exhaustive testing with finite generator
403
test("small numbers exhaustive") {
404
checkAll(int(1, 10)) { n =>
405
assert(n * n)(isGreaterThan(0))
406
}
407
}
408
409
// Fixed number of samples
410
test("large sample test") {
411
checkN(1000)(double) { d =>
412
assert(d + 0.0)(equalTo(d))
413
}
414
}
415
```