or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdcore-testing.mdindex.mdproperty-testing.mdsmart-assertions.mdtest-aspects.mdtest-services.md

test-services.mddocs/

0

# Test Services

1

2

Controllable test environment services that replace real services with deterministic, testable implementations. Test services enable predictable testing of time-dependent, I/O-dependent, and stateful code.

3

4

## Capabilities

5

6

### Test Environment

7

8

Core test environment type and management functions.

9

10

```scala { .api }

11

/**

12

* Standard test environment combining all test services

13

*/

14

type TestEnvironment = Annotations with Live with Sized with TestConfig

15

16

object TestEnvironment {

17

/** Layer providing TestEnvironment from existing environment */

18

val any: ZLayer[TestEnvironment, Nothing, TestEnvironment]

19

20

/** Layer providing TestEnvironment from live services */

21

val live: ZLayer[Clock with Console with System with Random, Nothing, TestEnvironment]

22

}

23

24

/**

25

* Live environment layer providing real services

26

*/

27

val liveEnvironment: Layer[Nothing, Clock with Console with System with Random]

28

29

/**

30

* Test environment layer providing all test services

31

*/

32

val testEnvironment: ZLayer[Any, Nothing, TestEnvironment]

33

```

34

35

**Usage Examples:**

36

37

```scala

38

import zio.test._

39

40

// Using test environment in specs

41

object MyTest extends ZIOSpecDefault {

42

def spec = suite("Test with environment")(

43

test("time-based test") {

44

for {

45

_ <- TestClock.adjust(1.hour)

46

time <- Clock.instant

47

} yield assertTrue(time.getEpochSecond > 0)

48

}

49

)

50

}

51

52

// Custom environment combining test and domain services

53

type MyTestEnv = TestEnvironment with MyService

54

55

val myTestLayer: ZLayer[Any, Nothing, MyTestEnv] =

56

testEnvironment ++ MyService.test

57

```

58

59

### TestClock Service

60

61

Controllable clock service for testing time-dependent operations.

62

63

```scala { .api }

64

/**

65

* Controllable clock for testing time-dependent code

66

*/

67

trait TestClock extends Clock {

68

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

69

def adjust(duration: Duration)(implicit trace: Trace): UIO[Unit]

70

71

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

72

def setTime(instant: Instant)(implicit trace: Trace): UIO[Unit]

73

74

/** Get all pending scheduled operations */

75

def sleeps(implicit trace: Trace): UIO[List[Duration]]

76

77

/** Save the current clock state */

78

def save(implicit trace: Trace): UIO[TestClock.Data]

79

80

/** Restore the clock to a previous state */

81

def restore(data: TestClock.Data)(implicit trace: Trace): UIO[Unit]

82

}

83

84

object TestClock {

85

/** Default TestClock layer */

86

val default: ZLayer[Any, Nothing, TestClock]

87

88

/** Data representing clock state for save/restore */

89

case class Data(instant: Instant, sleeps: List[Duration])

90

}

91

92

/**

93

* Access TestClock service

94

*/

95

def testClock(implicit trace: Trace): UIO[TestClock]

96

97

/**

98

* Access TestClock service and run workflow

99

*/

100

def testClockWith[R, E, A](f: TestClock => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

101

```

102

103

**Usage Examples:**

104

105

```scala

106

import zio.test._

107

108

test("time-dependent operations") {

109

for {

110

// Start a timed operation

111

fiber <- ZIO.sleep(1.hour).fork

112

113

// Verify it hasn't completed yet

114

isDone <- fiber.isDone

115

_ <- assertTrue(!isDone)

116

117

// Advance time

118

_ <- TestClock.adjust(1.hour)

119

120

// Verify operation completed

121

_ <- fiber.join

122

isDone <- fiber.isDone

123

} yield assertTrue(isDone)

124

}

125

126

test("clock manipulation") {

127

for {

128

// Set specific time

129

_ <- TestClock.setTime(Instant.ofEpochSecond(0))

130

131

// Check current time

132

now <- Clock.instant

133

_ <- assertTrue(now.getEpochSecond == 0)

134

135

// Advance time by specific amount

136

_ <- TestClock.adjust(Duration.ofMinutes(30))

137

138

// Verify time advanced

139

later <- Clock.instant

140

} yield assertTrue(later.getEpochSecond == 30 * 60)

141

}

142

```

143

144

### TestConsole Service

145

146

Controllable console service for testing I/O operations.

147

148

```scala { .api }

149

/**

150

* Controllable console for testing I/O operations

151

*/

152

trait TestConsole extends Console {

153

/** Feed input to the console for reading */

154

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

155

156

/** Get all output written to console */

157

def output(implicit trace: Trace): UIO[Vector[String]]

158

159

/** Clear all console output */

160

def clearOutput(implicit trace: Trace): UIO[Unit]

161

162

/** Get all error output written to console */

163

def errorOutput(implicit trace: Trace): UIO[Vector[String]]

164

165

/** Clear all console error output */

166

def clearErrorOutput(implicit trace: Trace): UIO[Unit]

167

168

/** Save the current console state */

169

def save(implicit trace: Trace): UIO[TestConsole.Data]

170

171

/** Restore console to previous state */

172

def restore(data: TestConsole.Data)(implicit trace: Trace): UIO[Unit]

173

}

174

175

object TestConsole {

176

/** Default TestConsole layer */

177

val default: ZLayer[Any, Nothing, TestConsole]

178

179

/** Debug TestConsole layer that prints to real console */

180

val debug: ZLayer[Any, Nothing, TestConsole]

181

182

/** Data representing console state for save/restore */

183

case class Data(input: List[String], output: Vector[String], errorOutput: Vector[String])

184

}

185

186

/**

187

* Access TestConsole service

188

*/

189

def testConsole(implicit trace: Trace): UIO[TestConsole]

190

191

/**

192

* Access TestConsole service and run workflow

193

*/

194

def testConsoleWith[R, E, A](f: TestConsole => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

195

```

196

197

**Usage Examples:**

198

199

```scala

200

import zio.test._

201

202

test("console input/output") {

203

for {

204

// Feed input for reading

205

_ <- TestConsole.feedLines("Alice", "25")

206

207

// Read input

208

name <- Console.readLine("Enter name: ")

209

age <- Console.readLine("Enter age: ")

210

211

// Write output

212

_ <- Console.printLine(s"Hello $name, you are $age years old")

213

214

// Verify output

215

output <- TestConsole.output

216

} yield assertTrue(

217

name == "Alice" &&

218

age == "25" &&

219

output.contains("Hello Alice, you are 25 years old")

220

)

221

}

222

223

test("error output") {

224

for {

225

// Write to error stream

226

_ <- Console.printLineError("Error occurred")

227

228

// Check error output

229

errors <- TestConsole.errorOutput

230

} yield assertTrue(errors.contains("Error occurred"))

231

}

232

```

233

234

### TestRandom Service

235

236

Controllable random number generator for deterministic testing.

237

238

```scala { .api }

239

/**

240

* Controllable random number generator for deterministic testing

241

*/

242

trait TestRandom extends Random {

243

/** Set the random seed */

244

def setSeed(seed: Long)(implicit trace: Trace): UIO[Unit]

245

246

/** Get the current random seed */

247

def getSeed(implicit trace: Trace): UIO[Long]

248

249

/** Feed specific values to be returned by random operations */

250

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

251

def feedLongs(longs: Long*)(implicit trace: Trace): UIO[Unit]

252

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

253

def feedFloats(floats: Float*)(implicit trace: Trace): UIO[Unit]

254

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

255

def feedStrings(strings: String*)(implicit trace: Trace): UIO[Unit]

256

def feedUUIDs(uuids: java.util.UUID*)(implicit trace: Trace): UIO[Unit]

257

def feedBytes(bytes: Chunk[Byte]*)(implicit trace: Trace): UIO[Unit]

258

259

/** Clear all fed values */

260

def clearInts(implicit trace: Trace): UIO[Unit]

261

def clearLongs(implicit trace: Trace): UIO[Unit]

262

def clearDoubles(implicit trace: Trace): UIO[Unit]

263

def clearFloats(implicit trace: Trace): UIO[Unit]

264

def clearBooleans(implicit trace: Trace): UIO[Unit]

265

def clearStrings(implicit trace: Trace): UIO[Unit]

266

def clearUUIDs(implicit trace: Trace): UIO[Unit]

267

def clearBytes(implicit trace: Trace): UIO[Unit]

268

269

/** Save the current random state */

270

def save(implicit trace: Trace): UIO[TestRandom.Data]

271

272

/** Restore random to previous state */

273

def restore(data: TestRandom.Data)(implicit trace: Trace): UIO[Unit]

274

}

275

276

object TestRandom {

277

/** Deterministic TestRandom layer */

278

val deterministic: ZLayer[Any, Nothing, TestRandom]

279

280

/** TestRandom layer with specific seed */

281

def seeded(seed: Long): ZLayer[Any, Nothing, TestRandom]

282

283

/** Data representing random state for save/restore */

284

case class Data(seed: Long, ints: List[Int], /* other fed values */)

285

}

286

287

/**

288

* Access TestRandom service

289

*/

290

def testRandom(implicit trace: Trace): UIO[TestRandom]

291

292

/**

293

* Access TestRandom service and run workflow

294

*/

295

def testRandomWith[R, E, A](f: TestRandom => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

296

```

297

298

**Usage Examples:**

299

300

```scala

301

import zio.test._

302

303

test("deterministic random values") {

304

for {

305

// Feed specific values

306

_ <- TestRandom.feedInts(42, 24, 7)

307

_ <- TestRandom.feedBooleans(true, false, true)

308

309

// Generate values (will use fed values)

310

n1 <- Random.nextInt

311

n2 <- Random.nextInt

312

n3 <- Random.nextInt

313

314

b1 <- Random.nextBoolean

315

b2 <- Random.nextBoolean

316

b3 <- Random.nextBoolean

317

} yield assertTrue(

318

n1 == 42 && n2 == 24 && n3 == 7 &&

319

b1 && !b2 && b3

320

)

321

}

322

323

test("seeded random") {

324

for {

325

// Set specific seed for reproducible tests

326

_ <- TestRandom.setSeed(12345L)

327

328

// Generate values (will be deterministic)

329

values <- ZIO.collectAll(List.fill(5)(Random.nextInt))

330

331

// Reset to same seed

332

_ <- TestRandom.setSeed(12345L)

333

334

// Generate same values again

335

sameValues <- ZIO.collectAll(List.fill(5)(Random.nextInt))

336

} yield assertTrue(values == sameValues)

337

}

338

```

339

340

### TestSystem Service

341

342

Controllable system environment for testing system interactions.

343

344

```scala { .api }

345

/**

346

* Controllable system environment for testing system interactions

347

*/

348

trait TestSystem extends System {

349

/** Set environment variable */

350

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

351

352

/** Remove environment variable */

353

def clearEnv(name: String)(implicit trace: Trace): UIO[Unit]

354

355

/** Set system property */

356

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

357

358

/** Remove system property */

359

def clearProperty(name: String)(implicit trace: Trace): UIO[Unit]

360

361

/** Set all environment variables */

362

def setEnvs(envs: Map[String, String])(implicit trace: Trace): UIO[Unit]

363

364

/** Set all system properties */

365

def setProperties(props: Map[String, String])(implicit trace: Trace): UIO[Unit]

366

367

/** Save the current system state */

368

def save(implicit trace: Trace): UIO[TestSystem.Data]

369

370

/** Restore system to previous state */

371

def restore(data: TestSystem.Data)(implicit trace: Trace): UIO[Unit]

372

}

373

374

object TestSystem {

375

/** Default TestSystem layer */

376

val default: ZLayer[Any, Nothing, TestSystem]

377

378

/** Data representing system state for save/restore */

379

case class Data(envs: Map[String, String], properties: Map[String, String])

380

}

381

382

/**

383

* Access TestSystem service

384

*/

385

def testSystem(implicit trace: Trace): UIO[TestSystem]

386

387

/**

388

* Access TestSystem service and run workflow

389

*/

390

def testSystemWith[R, E, A](f: TestSystem => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

391

```

392

393

**Usage Examples:**

394

395

```scala

396

import zio.test._

397

398

test("environment variables") {

399

for {

400

// Set environment variable

401

_ <- TestSystem.putEnv("MY_VAR", "test_value")

402

403

// Read environment variable

404

value <- System.env("MY_VAR")

405

406

// Clear environment variable

407

_ <- TestSystem.clearEnv("MY_VAR")

408

409

// Verify it's gone

410

cleared <- System.env("MY_VAR")

411

} yield assertTrue(

412

value.contains("test_value") &&

413

cleared.isEmpty

414

)

415

}

416

417

test("system properties") {

418

for {

419

// Set system property

420

_ <- TestSystem.putProperty("test.prop", "test_value")

421

422

// Read system property

423

value <- System.property("test.prop")

424

425

// Clear system property

426

_ <- TestSystem.clearProperty("test.prop")

427

428

// Verify it's gone

429

cleared <- System.property("test.prop")

430

} yield assertTrue(

431

value.contains("test_value") &&

432

cleared.isEmpty

433

)

434

}

435

```

436

437

### TestConfig Service

438

439

Test configuration service for controlling test execution parameters.

440

441

```scala { .api }

442

/**

443

* Test configuration service

444

*/

445

trait TestConfig {

446

/** Number of samples for property-based tests */

447

def samples: Int

448

449

/** Maximum number of shrinking iterations */

450

def shrinks: Int

451

452

/** Repeats configuration */

453

def repeats: Int

454

455

/** Retries configuration */

456

def retries: Int

457

458

/** Test aspect to apply to property tests */

459

def checkAspect: TestAspectPoly

460

}

461

462

object TestConfig {

463

/** Live TestConfig layer with specified parameters */

464

def live(samples: Int, shrinks: Int, repeats: Int, retries: Int): ZLayer[Any, Nothing, TestConfig]

465

466

/** Default TestConfig layer */

467

val default: ZLayer[Any, Nothing, TestConfig]

468

}

469

470

/**

471

* Access TestConfig service

472

*/

473

def testConfig(implicit trace: Trace): UIO[TestConfig]

474

475

/**

476

* Access TestConfig service and run workflow

477

*/

478

def testConfigWith[R, E, A](f: TestConfig => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

479

```

480

481

### Annotations Service

482

483

Service for managing test annotations and metadata.

484

485

```scala { .api }

486

/**

487

* Service for managing test annotations and metadata

488

*/

489

trait Annotations {

490

/** Get annotation value for the specified key */

491

def get[V](key: TestAnnotation[V])(implicit trace: Trace): UIO[V]

492

493

/** Annotate with key-value pair */

494

def annotate[V](key: TestAnnotation[V], value: V)(implicit trace: Trace): UIO[Unit]

495

496

/** Get all annotations as a map */

497

def annotated(implicit trace: Trace): UIO[TestAnnotationMap]

498

499

/** Run effect with additional annotation */

500

def withAnnotation[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

501

}

502

503

object Annotations {

504

/** Live Annotations layer */

505

val live: ZLayer[Any, Nothing, Annotations]

506

}

507

508

/**

509

* Access Annotations service

510

*/

511

def annotations(implicit trace: Trace): UIO[Annotations]

512

513

/**

514

* Access Annotations service and run workflow

515

*/

516

def annotationsWith[R, E, A](f: Annotations => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

517

```

518

519

### Sized Service

520

521

Service providing size parameter for generators.

522

523

```scala { .api }

524

/**

525

* Service providing size parameter for generators

526

*/

527

trait Sized {

528

/** Get the current size parameter */

529

def size(implicit trace: Trace): UIO[Int]

530

}

531

532

object Sized {

533

/** Live Sized layer with specified size */

534

def live(size: Int): ZLayer[Any, Nothing, Sized]

535

}

536

537

/**

538

* Access Sized service

539

*/

540

def sized(implicit trace: Trace): UIO[Sized]

541

542

/**

543

* Access Sized service and run workflow

544

*/

545

def sizedWith[R, E, A](f: Sized => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

546

```

547

548

### Live Service

549

550

Service for accessing live environment during tests.

551

552

```scala { .api }

553

/**

554

* Service for accessing live environment during tests

555

*/

556

trait Live {

557

/** Run effect with live environment instead of test environment */

558

def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

559

}

560

561

object Live {

562

/** Default Live layer */

563

val default: ZLayer[Any, Nothing, Live]

564

}

565

566

/**

567

* Access Live service

568

*/

569

def live(implicit trace: Trace): UIO[Live]

570

571

/**

572

* Access Live service and run workflow

573

*/

574

def liveWith[R, E, A](f: Live => ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

575

576

/**

577

* Run effect with live environment

578

*/

579

def live[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A]

580

581

/**

582

* Transform effect with live environment access

583

*/

584

def withLive[R, E, E1, A, B](zio: ZIO[R, E, A])(f: ZIO[R, E, A] => ZIO[R, E1, B])(implicit trace: Trace): ZIO[R, E1, B]

585

```

586

587

### Service Management Functions

588

589

Functions for managing and customizing test services.

590

591

```scala { .api }

592

/**

593

* Run workflow with custom annotations service

594

*/

595

def withAnnotations[R, E, A <: Annotations, B](annotations: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

596

597

/**

598

* Set annotations service in scope

599

*/

600

def withAnnotationsScoped[A <: Annotations](annotations: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

601

602

/**

603

* Run workflow with custom config service

604

*/

605

def withTestConfig[R, E, A <: TestConfig, B](testConfig: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

606

607

/**

608

* Set config service in scope

609

*/

610

def withTestConfigScoped[A <: TestConfig](testConfig: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

611

612

/**

613

* Run workflow with custom sized service

614

*/

615

def withSized[R, E, A <: Sized, B](sized: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

616

617

/**

618

* Set sized service in scope

619

*/

620

def withSizedScoped[A <: Sized](sized: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

621

622

/**

623

* Run workflow with custom live service

624

*/

625

def withLive[R, E, A <: Live, B](live: => A)(zio: => ZIO[R, E, B])(implicit tag: Tag[A], trace: Trace): ZIO[R, E, B]

626

627

/**

628

* Set live service in scope

629

*/

630

def withLiveScoped[A <: Live](live: => A)(implicit tag: Tag[A], trace: Trace): ZIO[Scope, Nothing, Unit]

631

```

632

633

**Usage Examples:**

634

635

```scala

636

import zio.test._

637

638

test("custom test configuration") {

639

val customConfig = new TestConfig {

640

def samples = 1000 // More samples than default

641

def shrinks = 50 // More shrinking iterations

642

def repeats = 1

643

def retries = 1

644

def checkAspect = TestAspect.identity

645

}

646

647

withTestConfig(customConfig) {

648

check(Gen.int) { n =>

649

assertTrue(n.isInstanceOf[Int])

650

}

651

}

652

}

653

654

test("custom size parameter") {

655

val largeSize = new Sized {

656

def size(implicit trace: Trace) = ZIO.succeed(200)

657

}

658

659

withSized(largeSize) {

660

check(Gen.sized(n => Gen.listOfN(n)(Gen.int))) { numbers =>

661

assertTrue(numbers.size <= 200)

662

}

663

}

664

}

665

```