or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdcore-dsl.mdindex.mdmock-testing.mdproperty-testing.mdspecifications.mdtest-aspects.mdtest-environment.md

property-testing.mddocs/

0

# Property-Based Testing

1

2

Sophisticated generator system for creating test data and verifying properties across input ranges. Property-based testing automatically generates test cases to verify that properties hold for a wide range of inputs.

3

4

## Capabilities

5

6

### Property Testing Functions

7

8

Core functions for running property-based tests.

9

10

```scala { .api }

11

/**

12

* Tests that a property holds for generated test data

13

* @param rv - Generator for test data

14

* @param test - Property test function

15

* @returns Effect that runs the property test

16

*/

17

def check[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]

18

19

/**

20

* Effectful property testing

21

* @param rv - Generator for test data

22

* @param test - Effectful property test function

23

* @returns Effect that runs the property test

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 property for all values from deterministic generator

29

* @param rv - Deterministic generator

30

* @param test - Property test function

31

* @returns Effect that tests all generated values

32

*/

33

def checkAll[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]

34

35

/**

36

* Effectful exhaustive property testing

37

*/

38

def checkAllM[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

39

40

/**

41

* Parallel exhaustive property testing

42

* @param parallelism - Number of parallel test executions

43

*/

44

def checkAllMPar[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A], parallelism: Int)(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

45

```

46

47

**Multi-Parameter Property Testing:**

48

49

```scala { .api }

50

// 2-parameter versions

51

def check[R <: TestConfig, A, B](rv1: Gen[R, A], rv2: Gen[R, B])(test: (A, B) => TestResult): URIO[R, TestResult]

52

def checkM[R <: TestConfig, R1 <: R, E, A, B](rv1: Gen[R, A], rv2: Gen[R, B])(test: (A, B) => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

53

54

// Similar overloads exist for 3-8 parameters

55

```

56

57

**Fixed-Sample Testing:**

58

59

```scala { .api }

60

/**

61

* Tests property for exactly N samples

62

* @param n - Number of samples to test

63

* @returns CheckN instance for chaining with generator

64

*/

65

def checkN(n: Int): CheckVariants.CheckN

66

67

/**

68

* Effectful fixed-sample testing

69

*/

70

def checkNM(n: Int): CheckVariants.CheckNM

71

```

72

73

**Usage Examples:**

74

75

```scala

76

import zio.test._

77

import zio.test.Assertion._

78

79

// Basic property test

80

test("list reverse is involutive") {

81

check(Gen.listOf(Gen.anyInt)) { list =>

82

assert(list.reverse.reverse)(equalTo(list))

83

}

84

}

85

86

// Multiple generators

87

test("addition is commutative") {

88

check(Gen.anyInt, Gen.anyInt) { (a, b) =>

89

assert(a + b)(equalTo(b + a))

90

}

91

}

92

93

// Fixed sample size

94

test("custom sample size") {

95

checkN(1000)(Gen.anyInt) { n =>

96

assert(n * 0)(equalTo(0))

97

}

98

}

99

100

// Effectful property test

101

testM("database operations") {

102

checkM(Gen.alphaNumericString) { userId =>

103

for {

104

_ <- UserRepository.create(userId)

105

found <- UserRepository.find(userId)

106

} yield assert(found)(isSome)

107

}

108

}

109

```

110

111

### Generator Creation and Combinators

112

113

Core generator class and its combinators.

114

115

```scala { .api }

116

/**

117

* Generator that produces samples of type A requiring environment R

118

*/

119

final case class Gen[-R, +A](sample: ZStream[R, Nothing, Sample[R, A]]) {

120

/**

121

* Map over generated values with type transformation

122

*/

123

def map[B](f: A => B): Gen[R, B]

124

125

/**

126

* FlatMap for composing generators

127

*/

128

def flatMap[R1 <: R, B](f: A => Gen[R1, B]): Gen[R1, B]

129

130

/**

131

* Filter generated values based on predicate

132

*/

133

def filter(f: A => Boolean): Gen[R, A]

134

135

/**

136

* Filter out values based on predicate

137

*/

138

def filterNot(f: A => Boolean): Gen[R, A]

139

140

/**

141

* Collect values using partial function

142

*/

143

def collect[B](pf: PartialFunction[A, B]): Gen[R, B]

144

145

/**

146

* Zip two generators together

147

*/

148

def zip[R1 <: R, B](that: Gen[R1, B]): Gen[R1, (A, B)]

149

150

/**

151

* Cross product of two generators (alias for zip)

152

*/

153

def <*>[R1 <: R, B](that: Gen[R1, B]): Gen[R1, (A, B)]

154

155

/**

156

* Zip with transformation function

157

*/

158

def zipWith[R1 <: R, B, C](that: Gen[R1, B])(f: (A, B) => C): Gen[R1, C]

159

}

160

```

161

162

### Primitive Generators

163

164

Built-in generators for basic types.

165

166

```scala { .api }

167

// Numeric generators

168

val anyByte: Gen[Any, Byte]

169

val anyChar: Gen[Any, Char]

170

val anyDouble: Gen[Any, Double]

171

val anyFloat: Gen[Any, Float]

172

val anyInt: Gen[Any, Int]

173

val anyLong: Gen[Any, Long]

174

175

// Bounded numeric generators

176

def byte(min: Byte, max: Byte): Gen[Any, Byte]

177

def char(min: Char, max: Char): Gen[Any, Char]

178

def double(min: Double, max: Double): Gen[Any, Double]

179

def float(min: Float, max: Float): Gen[Any, Float]

180

def int(min: Int, max: Int): Gen[Any, Int]

181

def long(min: Long, max: Long): Gen[Any, Long]

182

183

// Special numeric distributions

184

def exponential(min: Double, max: Double): Gen[Any, Double]

185

def uniform: Gen[Any, Double]

186

187

// Boolean and character generators

188

val boolean: Gen[Any, Boolean]

189

val printableChar: Gen[Any, Char]

190

val alphaChar: Gen[Any, Char]

191

val alphaNumericChar: Gen[Any, Char]

192

val hexChar: Gen[Any, Char]

193

```

194

195

**Usage Examples:**

196

197

```scala

198

// Range-based generators

199

val temperatures = Gen.double(-50.0, 50.0)

200

val dice = Gen.int(1, 6)

201

val lowercase = Gen.char('a', 'z')

202

203

// Composed generators

204

val coordinates = Gen.double(-180, 180).zip(Gen.double(-90, 90))

205

val userAge = Gen.int(13, 120).filter(_ >= 18) // Adults only

206

```

207

208

### String Generators

209

210

Generators for string types with various character sets.

211

212

```scala { .api }

213

// Basic string generators

214

val anyString: Gen[Any, String]

215

val alphaString: Gen[Sized, String]

216

val alphaNumericString: Gen[Sized, String]

217

val numericString: Gen[Sized, String]

218

219

// Custom string generators

220

def string(char: Gen[Any, Char]): Gen[Sized, String]

221

def string1(char: Gen[Any, Char]): Gen[Sized, String] // Non-empty

222

def stringN(n: Int)(char: Gen[Any, Char]): Gen[Any, String] // Fixed length

223

def stringBounded(min: Int, max: Int)(char: Gen[Any, Char]): Gen[Any, String]

224

```

225

226

**Usage Examples:**

227

228

```scala

229

// Email-like strings

230

val emailGen = for {

231

username <- Gen.stringBounded(3, 10)(Gen.alphaNumericChar)

232

domain <- Gen.stringBounded(3, 8)(Gen.alphaChar)

233

tld <- Gen.elements("com", "org", "net")

234

} yield s"$username@$domain.$tld"

235

236

// Password strings

237

val passwordGen = Gen.stringBounded(8, 20)(

238

Gen.oneOf(Gen.alphaChar, Gen.char('0', '9'), Gen.elements('!', '@', '#'))

239

)

240

```

241

242

### Collection Generators

243

244

Generators for collections with configurable sizing.

245

246

```scala { .api }

247

// List generators

248

def listOf[R, A](gen: Gen[R, A]): Gen[R with Sized, List[A]]

249

def listOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, List[A]] // Non-empty

250

def listOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, List[A]] // Fixed size

251

def listOfBounded[R, A](min: Int, max: Int)(gen: Gen[R, A]): Gen[R, List[A]]

252

253

// Set generators

254

def setOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]

255

def setOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Set[A]]

256

def setOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Set[A]]

257

258

// Vector generators

259

def vectorOf[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]

260

def vectorOf1[R, A](gen: Gen[R, A]): Gen[R with Sized, Vector[A]]

261

def vectorOfN[R, A](n: Int)(gen: Gen[R, A]): Gen[R, Vector[A]]

262

263

// Map generators

264

def mapOf[R, A, B](key: Gen[R, A], value: Gen[R, B]): Gen[R with Sized, Map[A, B]]

265

def mapOfN[R, A, B](n: Int)(key: Gen[R, A], value: Gen[R, B]): Gen[R, Map[A, B]]

266

def mapOfBounded[R, A, B](min: Int, max: Int)(key: Gen[R, A], value: Gen[R, B]): Gen[R, Map[A, B]]

267

```

268

269

**Usage Examples:**

270

271

```scala

272

// Generate test data sets

273

val userListGen = Gen.listOfBounded(1, 100)(userGen)

274

val uniqueIdsGen = Gen.setOfN(10)(Gen.uuid)

275

val configMapGen = Gen.mapOf(Gen.alphaNumericString, Gen.anyString)

276

277

// Nested collections

278

val matrixGen = Gen.listOfN(3)(Gen.listOfN(3)(Gen.double(0, 1)))

279

```

280

281

### Choice and Frequency Generators

282

283

Generators for selecting from predefined values or weighted distributions.

284

285

```scala { .api }

286

/**

287

* Choose one value from the provided options

288

*/

289

def oneOf[R, A](as: A*): Gen[R, A]

290

291

/**

292

* Alias for oneOf

293

*/

294

def elements[A](as: A*): Gen[Any, A]

295

296

/**

297

* Choose from an iterable collection

298

*/

299

def fromIterable[A](as: Iterable[A]): Gen[Any, A]

300

301

/**

302

* Weighted choice among generators

303

* @param gs - Tuples of (generator, weight)

304

*/

305

def weighted[R, A](gs: (Gen[R, A], Double)*): Gen[R, A]

306

307

/**

308

* Frequency-based choice among generators

309

* @param gs - Tuples of (frequency, generator)

310

*/

311

def frequency[R, A](gs: (Int, Gen[R, A])*): Gen[R, A]

312

```

313

314

**Usage Examples:**

315

316

```scala

317

// Status codes

318

val httpStatusGen = Gen.elements(200, 201, 400, 401, 404, 500)

319

320

// Weighted outcomes

321

val biasedCoinGen = Gen.weighted(

322

(Gen.const(true), 0.7), // 70% heads

323

(Gen.const(false), 0.3) // 30% tails

324

)

325

326

// Error scenarios with different frequencies

327

val responseGen = Gen.frequency(

328

(8, successResponseGen), // 80% success

329

(1, clientErrorGen), // 10% client error

330

(1, serverErrorGen) // 10% server error

331

)

332

```

333

334

### Option and Either Generators

335

336

Generators for optional and error-prone values.

337

338

```scala { .api }

339

/**

340

* Generate optional values

341

*/

342

def option[R, A](gen: Gen[R, A]): Gen[R, Option[A]]

343

344

/**

345

* Generate Some values only

346

*/

347

def some[R, A](gen: Gen[R, A]): Gen[R, Some[A]]

348

349

/**

350

* Generate None values

351

*/

352

val none: Gen[Any, Option[Nothing]]

353

354

/**

355

* Generate Either values

356

*/

357

def either[R, A, B](left: Gen[R, A], right: Gen[R, B]): Gen[R, Either[A, B]]

358

```

359

360

**Usage Examples:**

361

362

```scala

363

// Optional user profiles

364

val profileGen = Gen.option(userProfileGen)

365

366

// API responses that can fail

367

val apiResponseGen = Gen.either(

368

errorMessageGen, // Left for errors

369

responseDataGen // Right for success

370

)

371

```

372

373

### Utility Generators

374

375

Special-purpose generators and utilities.

376

377

```scala { .api }

378

/**

379

* Generator that always produces the same value

380

*/

381

def const[A](a: => A): Gen[Any, A]

382

383

/**

384

* Unit generator

385

*/

386

val unit: Gen[Any, Unit]

387

388

/**

389

* Size-based generator that uses current size setting

390

*/

391

def size: Gen[Sized, Int]

392

393

/**

394

* Suspend generator evaluation

395

*/

396

def suspend[R, A](gen: => Gen[R, A]): Gen[R, A]

397

398

/**

399

* Unfold generator from initial state

400

*/

401

def unfoldGen[R, S, A](s: S)(f: S => Gen[R, (S, A)]): Gen[R, A]

402

```

403

404

## Types

405

406

### Core Generator Types

407

408

```scala { .api }

409

/**

410

* Generator that produces samples of type A requiring environment R

411

*/

412

final case class Gen[-R, +A](sample: ZStream[R, Nothing, Sample[R, A]])

413

414

/**

415

* A sample value with shrinking stream for property test failure minimization

416

*/

417

final case class Sample[+R, +A](

418

value: A,

419

shrink: ZStream[R, Nothing, Sample[R, A]]

420

)

421

```

422

423

### Configuration Types

424

425

```scala { .api }

426

/**

427

* Test configuration service

428

*/

429

type TestConfig = Has[TestConfig.Service]

430

431

trait TestConfig.Service {

432

def repeats: Int // Number of test repetitions

433

def retries: Int // Number of retries for flaky tests

434

def samples: Int // Number of property test samples

435

def shrinks: Int // Maximum shrinking attempts

436

}

437

438

/**

439

* Size configuration for generators

440

*/

441

type Sized = Has[Sized.Service]

442

443

trait Sized.Service {

444

def size: UIO[Int]

445

def withSize[R, E, A](size: Int)(zio: ZIO[R, E, A]): ZIO[R, E, A]

446

}

447

```

448

449

### Check Variants

450

451

```scala { .api }

452

/**

453

* Fixed-sample property testing

454

*/

455

final class CheckN(n: Int) {

456

def apply[R <: TestConfig, A](rv: Gen[R, A])(test: A => TestResult): URIO[R, TestResult]

457

// Overloads for 2-6 parameters

458

}

459

460

/**

461

* Fixed-sample effectful property testing

462

*/

463

final class CheckNM(n: Int) {

464

def apply[R <: TestConfig, R1 <: R, E, A](rv: Gen[R, A])(test: A => ZIO[R1, E, TestResult]): ZIO[R1, E, TestResult]

465

// Overloads for 2-6 parameters

466

}

467

```