or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdconcurrency.mdcore-effects.mddependency-injection.mderror-handling.mdindex.mdmetrics.mdresource-management.mdservices.mdstm.mdstreams.mdtesting.md

testing.mddocs/

0

# ZIO Test Framework

1

2

ZIO Test provides a comprehensive testing framework with property-based testing, test services, and seamless integration with the ZIO effect system for testing concurrent and async code.

3

4

## Capabilities

5

6

### Test Specifications

7

8

Define test suites and individual test cases with full ZIO effect support and dependency injection.

9

10

```scala { .api }

11

/**

12

* A test specification defines a suite of tests

13

*/

14

sealed trait Spec[-R, +E] {

15

/** Transform the environment type */

16

def provideLayer[E1 >: E, R0](layer: ZLayer[R0, E1, R]): Spec[R0, E1]

17

18

/** Add test aspects to modify test behavior */

19

def @@[R1 <: R](aspect: TestAspect[R1, E]): Spec[R1, E]

20

21

/** Map over the error type */

22

def mapError[E2](f: E => E2): Spec[R, E2]

23

}

24

25

/**

26

* Base class for ZIO test suites

27

*/

28

abstract class ZIOSpec[R] extends ZIOApp {

29

/** Define the test specification */

30

def spec: Spec[R, Any]

31

32

/** Provide dependencies for tests */

33

def bootstrap: ZLayer[Any, Any, R] = ZLayer.empty

34

}

35

36

/**

37

* Default test suite with no special requirements

38

*/

39

abstract class ZIOSpecDefault extends ZIOSpec[Any] {

40

final def bootstrap: ZLayer[Any, Any, Any] = ZLayer.empty

41

}

42

43

/**

44

* Test construction methods

45

*/

46

object ZIOSpecDefault {

47

/** Create a simple test */

48

def test(label: String)(assertion: => TestResult): Spec[Any, Nothing]

49

50

/** Create a test with full ZIO effects */

51

def testM[R, E](label: String)(assertion: ZIO[R, E, TestResult]): Spec[R, E]

52

53

/** Create a test suite */

54

def suite(label: String)(specs: Spec[Any, Any]*): Spec[Any, Any]

55

56

/** Create a test suite with environment requirements */

57

def suiteM[R, E](label: String)(specs: ZIO[R, E, Spec[R, E]]): Spec[R, E]

58

}

59

```

60

61

**Usage Examples:**

62

63

```scala

64

import zio._

65

import zio.test._

66

67

object MyServiceSpec extends ZIOSpecDefault {

68

def spec = suite("MyService")(

69

test("should handle simple operations") {

70

val result = 2 + 2

71

assertTrue(result == 4)

72

},

73

74

test("should work with ZIO effects") {

75

for {

76

result <- ZIO.succeed(42)

77

} yield assertTrue(result > 0)

78

},

79

80

suite("error handling")(

81

test("should catch failures") {

82

val failing = ZIO.fail("error")

83

assertZIO(failing.flip)(equalTo("error"))

84

}

85

)

86

)

87

}

88

89

// With dependency injection

90

object DatabaseSpec extends ZIOSpec[Database] {

91

def spec = suite("Database")(

92

test("should save and retrieve users") {

93

for {

94

db <- ZIO.service[Database]

95

user <- db.save(User("alice", "alice@example.com"))

96

retrieved <- db.findById(user.id)

97

} yield assertTrue(retrieved.contains(user))

98

}

99

)

100

101

def bootstrap = DatabaseLive.layer

102

}

103

```

104

105

### Assertions

106

107

Comprehensive assertion library for testing values, effects, and complex conditions.

108

109

```scala { .api }

110

/**

111

* Represents the result of a single test

112

*/

113

sealed trait TestResult {

114

/** Combine with another test result using logical AND */

115

def &&(that: => TestResult): TestResult

116

117

/** Combine with another test result using logical OR */

118

def ||(that: => TestResult): TestResult

119

120

/** Negate the test result */

121

def unary_! : TestResult

122

123

/** Add a custom label to the result */

124

def label(label: String): TestResult

125

}

126

127

/**

128

* Type-safe assertions for testing

129

*/

130

sealed trait Assertion[-A] {

131

/** Test the assertion against a value */

132

def test(a: A): TestResult

133

134

/** Combine with another assertion using logical AND */

135

def &&[A1 <: A](that: Assertion[A1]): Assertion[A1]

136

137

/** Combine with another assertion using logical OR */

138

def ||[A1 <: A](that: Assertion[A1]): Assertion[A1]

139

140

/** Negate the assertion */

141

def unary_! : Assertion[A]

142

}

143

144

/**

145

* Common assertion constructors

146

*/

147

object Assertion {

148

/** Assert equality */

149

def equalTo[A](expected: A): Assertion[A]

150

151

/** Assert approximate equality for numbers */

152

def approximatelyEquals(expected: Double, tolerance: Double): Assertion[Double]

153

154

/** Assert value is true */

155

val isTrue: Assertion[Boolean]

156

157

/** Assert value is false */

158

val isFalse: Assertion[Boolean]

159

160

/** Assert value is empty (for collections) */

161

val isEmpty: Assertion[Iterable[Any]]

162

163

/** Assert value is non-empty */

164

val isNonEmpty: Assertion[Iterable[Any]]

165

166

/** Assert collection contains element */

167

def contains[A](element: A): Assertion[Iterable[A]]

168

169

/** Assert string contains substring */

170

def containsString(substring: String): Assertion[String]

171

172

/** Assert string starts with prefix */

173

def startsWith(prefix: String): Assertion[String]

174

175

/** Assert string ends with suffix */

176

def endsWith(suffix: String): Assertion[String]

177

178

/** Assert string matches regex */

179

def matchesRegex(regex: String): Assertion[String]

180

181

/** Assert value is greater than */

182

def isGreaterThan[A](expected: A)(implicit ord: Ordering[A]): Assertion[A]

183

184

/** Assert value is less than */

185

def isLessThan[A](expected: A)(implicit ord: Ordering[A]): Assertion[A]

186

187

/** Assert value is within range */

188

def isWithin[A](min: A, max: A)(implicit ord: Ordering[A]): Assertion[A]

189

190

/** Assert all elements satisfy condition */

191

def forall[A](assertion: Assertion[A]): Assertion[Iterable[A]]

192

193

/** Assert at least one element satisfies condition */

194

def exists[A](assertion: Assertion[A]): Assertion[Iterable[A]]

195

196

/** Assert collection has specific size */

197

def hasSize[A](expected: Int): Assertion[Iterable[A]]

198

}

199

200

/**

201

* Convenient assertion methods

202

*/

203

def assertTrue(condition: => Boolean): TestResult

204

def assert[A](value: A)(assertion: Assertion[A]): TestResult

205

def assertZIO[R, E, A](effect: ZIO[R, E, A])(assertion: Assertion[A]): ZIO[R, E, TestResult]

206

```

207

208

**Usage Examples:**

209

210

```scala

211

// Basic assertions

212

test("basic assertions") {

213

assertTrue(2 + 2 == 4) &&

214

assert("hello world")(containsString("world")) &&

215

assert(List(1, 2, 3))(hasSize(3) && contains(2))

216

}

217

218

// Effect assertions

219

test("effect assertions") {

220

val computation = ZIO.succeed(42)

221

assertZIO(computation)(isGreaterThan(40))

222

}

223

224

// Complex conditions

225

test("complex conditions") {

226

val data = List("apple", "banana", "cherry")

227

assert(data)(

228

hasSize(3) &&

229

forall(startsWith("a") || startsWith("b") || startsWith("c")) &&

230

exists(containsString("nan"))

231

)

232

}

233

234

// Custom labels

235

test("with custom labels") {

236

val result = complexCalculation()

237

assert(result)(isGreaterThan(0)).label("result should be positive") &&

238

assert(result)(isLessThan(100)).label("result should be reasonable")

239

}

240

```

241

242

### Property-Based Testing

243

244

Generate random test data and verify properties hold across many test cases.

245

246

```scala { .api }

247

/**

248

* Generator for producing random test data

249

*/

250

sealed trait Gen[+R, +A] {

251

/** Transform generated values */

252

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

253

254

/** Chain generators together */

255

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

256

257

/** Filter generated values */

258

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

259

260

/** Generate optional values */

261

def optional: Gen[R, Option[A]]

262

263

/** Generate lists of values */

264

def list: Gen[R, List[A]]

265

266

/** Generate lists of specific size */

267

def listOfN(n: Int): Gen[R, List[A]]

268

269

/** Generate values between bounds */

270

def bounded(min: A, max: A)(implicit ord: Ordering[A]): Gen[R, A]

271

}

272

273

/**

274

* Common generators

275

*/

276

object Gen {

277

/** Generate random integers */

278

val int: Gen[Any, Int]

279

280

/** Generate integers in range */

281

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

282

283

/** Generate random strings */

284

val string: Gen[Any, String]

285

286

/** Generate alphanumeric strings */

287

val alphaNumericString: Gen[Any, String]

288

289

/** Generate strings of specific length */

290

def stringN(n: Int): Gen[Any, String]

291

292

/** Generate random booleans */

293

val boolean: Gen[Any, Boolean]

294

295

/** Generate random doubles */

296

val double: Gen[Any, Double]

297

298

/** Generate from a list of options */

299

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

300

301

/** Generate from weighted options */

302

def weighted[A](weightedValues: (A, Double)*): Gen[Any, A]

303

304

/** Generate constant values */

305

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

306

307

/** Generate collections */

308

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

309

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

310

def mapOf[R, K, V](keyGen: Gen[R, K], valueGen: Gen[R, V]): Gen[R, Map[K, V]]

311

312

/** Generate case classes */

313

def zip[R, A, B](genA: Gen[R, A], genB: Gen[R, B]): Gen[R, (A, B)]

314

def zip3[R, A, B, C](genA: Gen[R, A], genB: Gen[R, B], genC: Gen[R, C]): Gen[R, (A, B, C)]

315

}

316

317

/**

318

* Property-based test construction

319

*/

320

def check[R, A](gen: Gen[R, A])(test: A => TestResult): ZIO[R, Nothing, TestResult]

321

def checkAll[R, A](gen: Gen[R, A])(test: A => TestResult): ZIO[R, Nothing, TestResult]

322

def checkN(n: Int): CheckN

323

```

324

325

**Usage Examples:**

326

327

```scala

328

import zio.test.Gen._

329

330

// Simple property test

331

test("string reverse property") {

332

check(string) { s =>

333

assertTrue(s.reverse.reverse == s)

334

}

335

}

336

337

// Multiple generators

338

test("addition is commutative") {

339

check(int, int) { (a, b) =>

340

assertTrue(a + b == b + a)

341

}

342

}

343

344

// Custom generators

345

val positiveInt = int(1, 1000)

346

val email = for {

347

name <- alphaNumericString

348

domain <- oneOf("gmail.com", "yahoo.com", "example.org")

349

} yield s"$name@$domain"

350

351

test("email validation") {

352

check(email) { email =>

353

assertTrue(email.contains("@") && email.contains("."))

354

}

355

}

356

357

// Complex data structures

358

case class User(name: String, age: Int, email: String)

359

360

val userGen = for {

361

name <- alphaNumericString

362

age <- int(18, 100)

363

email <- email

364

} yield User(name, age, email)

365

366

test("user serialization") {

367

check(userGen) { user =>

368

val serialized = serialize(user)

369

val deserialized = deserialize(serialized)

370

assertTrue(deserialized == user)

371

}

372

}

373

```

374

375

### Test Services

376

377

Mock implementations of ZIO services for deterministic testing without external dependencies.

378

379

```scala { .api }

380

/**

381

* Test implementation of Clock service for deterministic time testing

382

*/

383

trait TestClock extends Clock {

384

/** Adjust the test clock by a duration */

385

def adjust(duration: Duration): UIO[Unit]

386

387

/** Set the test clock to a specific instant */

388

def setTime(instant: Instant): UIO[Unit]

389

390

/** Get current test time */

391

def instant: UIO[Instant]

392

393

/** Get time zone */

394

def timeZone: UIO[ZoneId]

395

}

396

397

/**

398

* Test implementation of Console for capturing output

399

*/

400

trait TestConsole extends Console {

401

/** Get all output written to stdout */

402

def output: UIO[Vector[String]]

403

404

/** Get all output written to stderr */

405

def errorOutput: UIO[Vector[String]]

406

407

/** Clear all captured output */

408

def clearOutput: UIO[Unit]

409

410

/** Feed input to be read by readLine */

411

def feedLines(lines: String*): UIO[Unit]

412

}

413

414

/**

415

* Test implementation of Random for predictable randomness

416

*/

417

trait TestRandom extends Random {

418

/** Set the random seed */

419

def setSeed(seed: Long): UIO[Unit]

420

421

/** Feed specific random values */

422

def feedInts(ints: Int*): UIO[Unit]

423

def feedDoubles(doubles: Double*): UIO[Unit]

424

def feedBooleans(booleans: Boolean*): UIO[Unit]

425

}

426

427

/**

428

* Test implementation of System for mocking environment

429

*/

430

trait TestSystem extends System {

431

/** Set environment variables */

432

def putEnv(name: String, value: String): UIO[Unit]

433

434

/** Set system properties */

435

def putProperty(name: String, value: String): UIO[Unit]

436

437

/** Clear environment variables */

438

def clearEnv(name: String): UIO[Unit]

439

440

/** Clear system properties */

441

def clearProperty(name: String): UIO[Unit]

442

}

443

```

444

445

**Usage Examples:**

446

447

```scala

448

// Testing with TestClock

449

test("timeout behavior") {

450

for {

451

fiber <- longRunningTask.timeout(5.seconds).fork

452

_ <- TestClock.adjust(6.seconds)

453

result <- fiber.join

454

} yield assertTrue(result.isEmpty)

455

}

456

457

// Testing with TestConsole

458

test("console output") {

459

for {

460

_ <- Console.printLine("Hello")

461

_ <- Console.printLine("World")

462

output <- TestConsole.output

463

} yield assertTrue(output == Vector("Hello", "World"))

464

}

465

466

// Testing with TestRandom

467

test("random behavior") {

468

for {

469

_ <- TestRandom.feedInts(1, 2, 3)

470

first <- Random.nextInt

471

second <- Random.nextInt

472

third <- Random.nextInt

473

} yield assertTrue(first == 1 && second == 2 && third == 3)

474

}

475

476

// Combined service testing

477

test("application behavior") {

478

val program = for {

479

config <- ZIO.service[AppConfig]

480

_ <- Console.printLine(s"Starting on port ${config.port}")

481

_ <- Clock.sleep(1.second)

482

_ <- Console.printLine("Started successfully")

483

} yield ()

484

485

program.provide(

486

TestConsole.layer,

487

TestClock.layer,

488

ZLayer.succeed(AppConfig(8080))

489

) *>

490

for {

491

_ <- TestClock.adjust(1.second)

492

output <- TestConsole.output

493

} yield assertTrue(

494

output.contains("Starting on port 8080") &&

495

output.contains("Started successfully")

496

)

497

}

498

```

499

500

### Test Aspects

501

502

Modify test behavior with reusable aspects for timeouts, retries, parallelism, and more.

503

504

```scala { .api }

505

/**

506

* Test aspects modify how tests are executed

507

*/

508

sealed trait TestAspect[-R, +E] {

509

/** Combine with another aspect */

510

def @@[R1 <: R](that: TestAspect[R1, E]): TestAspect[R1, E]

511

}

512

513

/**

514

* Common test aspects

515

*/

516

object TestAspect {

517

/** Set timeout for tests */

518

def timeout(duration: Duration): TestAspect[Any, Nothing]

519

520

/** Retry failed tests */

521

def retry(n: Int): TestAspect[Any, Nothing]

522

523

/** Run tests eventually (keep retrying until success) */

524

val eventually: TestAspect[Any, Nothing]

525

526

/** Run tests in parallel */

527

val parallel: TestAspect[Any, Nothing]

528

529

/** Run tests sequentially */

530

val sequential: TestAspect[Any, Nothing]

531

532

/** Ignore/skip tests */

533

val ignore: TestAspect[Any, Nothing]

534

535

/** Run only on specific platforms */

536

def jvmOnly: TestAspect[Any, Nothing]

537

def jsOnly: TestAspect[Any, Nothing]

538

def nativeOnly: TestAspect[Any, Nothing]

539

540

/** Repeat tests multiple times */

541

def repeats(n: Int): TestAspect[Any, Nothing]

542

543

/** Add samples for property-based tests */

544

def samples(n: Int): TestAspect[Any, Nothing]

545

546

/** Shrink failing test cases */

547

val shrinks: TestAspect[Any, Nothing]

548

549

/** Run with specific test data */

550

def withLiveClock: TestAspect[Any, Nothing]

551

def withLiveConsole: TestAspect[Any, Nothing]

552

def withLiveRandom: TestAspect[Any, Nothing]

553

}

554

```

555

556

**Usage Examples:**

557

558

```scala

559

// Timeout aspect

560

test("long running operation") {

561

heavyComputation

562

} @@ TestAspect.timeout(30.seconds)

563

564

// Retry flaky tests

565

test("flaky network operation") {

566

networkCall

567

} @@ TestAspect.retry(3)

568

569

// Eventually succeeding tests

570

test("eventual consistency") {

571

checkEventualConsistency

572

} @@ TestAspect.eventually

573

574

// Platform-specific tests

575

test("JVM-specific functionality") {

576

jvmSpecificCode

577

} @@ TestAspect.jvmOnly

578

579

// Property test configuration

580

test("complex property") {

581

check(complexGen)(complexProperty)

582

} @@ TestAspect.samples(1000) @@ TestAspect.shrinks

583

584

// Suite-level aspects

585

suite("integration tests")(

586

test("database operations") { ... },

587

test("api calls") { ... }

588

) @@ TestAspect.sequential @@ TestAspect.timeout(5.minutes)

589

```