or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdconfiguration.mdindex.mdproperty-testing.mdtest-aspects.mdtest-definition.mdtest-services.md

test-services.mddocs/

0

# Test Services

1

2

Deterministic test services that replace system services during testing for predictable, reproducible test execution.

3

4

## Capabilities

5

6

### TestClock

7

8

Deterministic clock service for controlling time progression in tests.

9

10

```scala { .api }

11

/**

12

* Test clock that allows manual time control

13

*/

14

trait TestClock extends Clock {

15

/**

16

* Advance the clock by the specified duration

17

* @param duration amount to advance time

18

* @return effect that advances the clock

19

*/

20

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

21

22

/**

23

* Set the clock to an absolute time

24

* @param duration absolute time from epoch

25

* @return effect that sets the clock time

26

*/

27

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

28

29

/**

30

* Get the current time zone

31

* @return current time zone

32

*/

33

def timeZone: UIO[ZoneId]

34

35

/**

36

* Set the time zone for the clock

37

* @param zone new time zone

38

* @return effect that sets the time zone

39

*/

40

def setTimeZone(zone: ZoneId): UIO[Unit]

41

42

/**

43

* Sleep for the specified duration (uses test time)

44

* @param duration duration to sleep

45

* @return effect that completes after the duration

46

*/

47

override def sleep(duration: Duration): UIO[Unit]

48

49

/**

50

* Get current time in specified time unit

51

* @param unit time unit for the result

52

* @return current time in the specified unit

53

*/

54

override def currentTime(unit: TimeUnit): UIO[Long]

55

56

/**

57

* Get current time as instant

58

* @return current time instant

59

*/

60

override def instant: UIO[Instant]

61

}

62

63

object TestClock {

64

/**

65

* Default test clock implementation starting at epoch

66

*/

67

val default: ULayer[TestClock]

68

69

/**

70

* Create test clock starting at specified time

71

* @param startTime initial clock time

72

* @return layer providing test clock

73

*/

74

def live(startTime: Instant): ULayer[TestClock]

75

}

76

```

77

78

**Usage Examples:**

79

80

```scala

81

import zio.test._

82

import zio._

83

import java.time.Instant

84

import java.util.concurrent.TimeUnit

85

86

// Basic time manipulation

87

test("time-dependent operation") {

88

for {

89

clock <- ZIO.service[TestClock]

90

start <- clock.currentTime(TimeUnit.MILLISECONDS)

91

_ <- clock.adjust(1.hour)

92

end <- clock.currentTime(TimeUnit.MILLISECONDS)

93

} yield assertTrue(end - start == 3600000)

94

}

95

96

// Testing scheduled operations

97

test("scheduled task execution") {

98

for {

99

clock <- ZIO.service[TestClock]

100

ref <- Ref.make(0)

101

// Schedule task to run every minute

102

fiber <- (ZIO.sleep(1.minute) *> ref.update(_ + 1)).forever.fork

103

// Advance time and check execution

104

_ <- clock.adjust(5.minutes)

105

count <- ref.get

106

_ <- fiber.interrupt

107

} yield assertTrue(count == 5)

108

}

109

110

// Testing timeout behavior

111

test("operation timeout") {

112

for {

113

clock <- ZIO.service[TestClock]

114

fiber <- (ZIO.sleep(10.seconds) *> ZIO.succeed("completed")).fork

115

_ <- clock.adjust(5.seconds)

116

result <- fiber.poll

117

} yield assertTrue(result.isEmpty) // Should still be running

118

}

119

```

120

121

### TestConsole

122

123

Console service for testing input/output operations with configurable responses.

124

125

```scala { .api }

126

/**

127

* Test console that captures output and provides controllable input

128

*/

129

trait TestConsole extends Console {

130

/**

131

* Get all captured output lines

132

* @return vector of output lines

133

*/

134

def output: UIO[Vector[String]]

135

136

/**

137

* Clear all captured output

138

* @return effect that clears output buffer

139

*/

140

def clearOutput: UIO[Unit]

141

142

/**

143

* Provide input lines for console reading

144

* @param lines input lines to feed to console

145

* @return effect that feeds the lines

146

*/

147

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

148

149

/**

150

* Get all captured error output

151

* @return vector of error output lines

152

*/

153

def errorOutput: UIO[Vector[String]]

154

155

/**

156

* Clear all captured error output

157

* @return effect that clears error output buffer

158

*/

159

def clearErrorOutput: UIO[Unit]

160

161

/**

162

* Print line to output (captured)

163

* @param line line to print

164

* @return effect that prints the line

165

*/

166

override def printLine(line: Any): IO[IOException, Unit]

167

168

/**

169

* Print to output without newline (captured)

170

* @param text text to print

171

* @return effect that prints the text

172

*/

173

override def print(text: Any): IO[IOException, Unit]

174

175

/**

176

* Read line from input (uses fed lines)

177

* @return effect that reads a line

178

*/

179

override def readLine: IO[IOException, String]

180

}

181

182

object TestConsole {

183

/**

184

* Debug console that prints to real console and captures

185

*/

186

val debug: ULayer[TestConsole]

187

188

/**

189

* Silent console that only captures without real output

190

*/

191

val silent: ULayer[TestConsole]

192

}

193

```

194

195

**Usage Examples:**

196

197

```scala

198

import zio.test._

199

import zio._

200

import java.io.IOException

201

202

// Capture and verify output

203

test("application output") {

204

for {

205

_ <- Console.printLine("Hello, World!")

206

_ <- Console.printLine("ZIO Test is awesome!")

207

console <- ZIO.service[TestConsole]

208

output <- console.output

209

} yield assertTrue(

210

output == Vector("Hello, World!", "ZIO Test is awesome!")

211

)

212

}

213

214

// Test interactive console programs

215

test("interactive program") {

216

def interactiveProgram: ZIO[Console, IOException, String] =

217

for {

218

_ <- Console.printLine("What's your name?")

219

name <- Console.readLine

220

_ <- Console.printLine(s"Hello, $name!")

221

} yield name

222

223

for {

224

console <- ZIO.service[TestConsole]

225

_ <- console.feedLines("Alice")

226

result <- interactiveProgram

227

output <- console.output

228

} yield assertTrue(

229

result == "Alice" &&

230

output == Vector("What's your name?", "Hello, Alice!")

231

)

232

}

233

234

// Test error output

235

test("error logging") {

236

def logError(message: String): ZIO[Console, IOException, Unit] =

237

Console.printLineError(s"ERROR: $message")

238

239

for {

240

_ <- logError("Something went wrong")

241

console <- ZIO.service[TestConsole]

242

errorOutput <- console.errorOutput

243

} yield assertTrue(errorOutput == Vector("ERROR: Something went wrong"))

244

}

245

```

246

247

### TestRandom

248

249

Deterministic random service for reproducible random number generation in tests.

250

251

```scala { .api }

252

/**

253

* Test random service with controllable seed and predefined values

254

*/

255

trait TestRandom extends Random {

256

/**

257

* Set the random seed for reproducible generation

258

* @param seed random seed value

259

* @return effect that sets the seed

260

*/

261

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

262

263

/**

264

* Feed predefined boolean values

265

* @param booleans boolean values to return from nextBoolean

266

* @return effect that feeds the values

267

*/

268

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

269

270

/**

271

* Clear all fed boolean values

272

* @return effect that clears boolean buffer

273

*/

274

def clearBooleans: UIO[Unit]

275

276

/**

277

* Feed predefined integer values

278

* @param ints integer values to return from nextInt

279

* @return effect that feeds the values

280

*/

281

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

282

283

/**

284

* Clear all fed integer values

285

* @return effect that clears integer buffer

286

*/

287

def clearInts: UIO[Unit]

288

289

/**

290

* Feed predefined long values

291

* @param longs long values to return from nextLong

292

* @return effect that feeds the values

293

*/

294

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

295

296

/**

297

* Clear all fed long values

298

* @return effect that clears long buffer

299

*/

300

def clearLongs: UIO[Unit]

301

302

/**

303

* Feed predefined double values

304

* @param doubles double values to return from nextDouble

305

* @return effect that feeds the values

306

*/

307

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

308

309

/**

310

* Clear all fed double values

311

* @return effect that clears double buffer

312

*/

313

def clearDoubles: UIO[Unit]

314

315

/**

316

* Generate next boolean (uses fed values or seed-based generation)

317

* @return effect producing boolean value

318

*/

319

override def nextBoolean: UIO[Boolean]

320

321

/**

322

* Generate next integer in range (uses fed values or seed-based generation)

323

* @param n upper bound (exclusive)

324

* @return effect producing integer value

325

*/

326

override def nextInt(n: Int): UIO[Int]

327

328

/**

329

* Generate next long (uses fed values or seed-based generation)

330

* @return effect producing long value

331

*/

332

override def nextLong: UIO[Long]

333

334

/**

335

* Generate next double (uses fed values or seed-based generation)

336

* @return effect producing double value

337

*/

338

override def nextDouble: UIO[Double]

339

}

340

341

object TestRandom {

342

/**

343

* Deterministic random with fixed seed

344

* @param seed initial seed value

345

* @return layer providing test random

346

*/

347

def deterministic(seed: Long): ULayer[TestRandom]

348

349

/**

350

* Deterministic random with default seed (0)

351

*/

352

val deterministic: ULayer[TestRandom]

353

}

354

```

355

356

**Usage Examples:**

357

358

```scala

359

import zio.test._

360

import zio._

361

362

// Reproducible random testing

363

test("random behavior with fixed seed") {

364

def randomOperation: ZIO[Random, Nothing, List[Int]] =

365

ZIO.collectAll(List.fill(5)(Random.nextInt(100)))

366

367

for {

368

random <- ZIO.service[TestRandom]

369

_ <- random.setSeed(12345L)

370

result1 <- randomOperation

371

_ <- random.setSeed(12345L)

372

result2 <- randomOperation

373

} yield assertTrue(result1 == result2) // Same seed = same results

374

}

375

376

// Control random values explicitly

377

test("specific random sequence") {

378

def coinFlips(n: Int): ZIO[Random, Nothing, List[String]] =

379

ZIO.collectAll(

380

List.fill(n)(Random.nextBoolean.map(if (_) "heads" else "tails"))

381

)

382

383

for {

384

random <- ZIO.service[TestRandom]

385

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

386

flips <- coinFlips(5)

387

} yield assertTrue(

388

flips == List("heads", "tails", "heads", "heads", "tails")

389

)

390

}

391

392

// Test random distributions

393

test("random distribution properties") {

394

def generateSample: ZIO[Random, Nothing, List[Int]] =

395

ZIO.collectAll(List.fill(1000)(Random.nextInt(100)))

396

397

for {

398

random <- ZIO.service[TestRandom]

399

_ <- random.setSeed(42L)

400

sample <- generateSample

401

average = sample.sum.toDouble / sample.size

402

} yield assertTrue(average >= 40.0 && average <= 60.0) // Should be around 50

403

}

404

```

405

406

### TestSystem

407

408

System service for testing environment variables and system properties.

409

410

```scala { .api }

411

/**

412

* Test system service with controllable environment and properties

413

*/

414

trait TestSystem extends System {

415

/**

416

* Set environment variable for testing

417

* @param name variable name

418

* @param value variable value

419

* @return effect that sets the variable

420

*/

421

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

422

423

/**

424

* Clear environment variable

425

* @param name variable name to clear

426

* @return effect that clears the variable

427

*/

428

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

429

430

/**

431

* Set system property for testing

432

* @param name property name

433

* @param value property value

434

* @return effect that sets the property

435

*/

436

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

437

438

/**

439

* Clear system property

440

* @param name property name to clear

441

* @return effect that clears the property

442

*/

443

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

444

445

/**

446

* Get environment variable

447

* @param name variable name

448

* @return effect producing optional variable value

449

*/

450

override def env(name: String): IO[SecurityException, Option[String]]

451

452

/**

453

* Get system property

454

* @param name property name

455

* @return effect producing optional property value

456

*/

457

override def property(name: String): IO[Throwable, Option[String]]

458

459

/**

460

* Get line separator for current system

461

* @return line separator string

462

*/

463

override def lineSeparator: UIO[String]

464

}

465

466

object TestSystem {

467

/**

468

* Default test system implementation

469

*/

470

val default: ULayer[TestSystem]

471

472

/**

473

* Test system with initial environment and properties

474

* @param env initial environment variables

475

* @param props initial system properties

476

* @return layer providing test system

477

*/

478

def live(

479

env: Map[String, String] = Map.empty,

480

props: Map[String, String] = Map.empty

481

): ULayer[TestSystem]

482

}

483

```

484

485

**Usage Examples:**

486

487

```scala

488

import zio.test._

489

import zio._

490

491

// Test environment-dependent behavior

492

test("application configuration from environment") {

493

def getConfig: ZIO[System, SecurityException, AppConfig] =

494

for {

495

host <- System.env("DB_HOST").map(_.getOrElse("localhost"))

496

port <- System.env("DB_PORT").map(_.getOrElse("5432"))

497

} yield AppConfig(host, port.toInt)

498

499

for {

500

system <- ZIO.service[TestSystem]

501

_ <- system.putEnv("DB_HOST", "test-db.example.com")

502

_ <- system.putEnv("DB_PORT", "3306")

503

config <- getConfig

504

} yield assertTrue(

505

config.host == "test-db.example.com" &&

506

config.port == 3306

507

)

508

}

509

510

// Test system property behavior

511

test("debug mode from system property") {

512

def isDebugMode: ZIO[System, Throwable, Boolean] =

513

System.property("debug").map(_.contains("true"))

514

515

for {

516

system <- ZIO.service[TestSystem]

517

_ <- system.putProperty("debug", "true")

518

debug1 <- isDebugMode

519

_ <- system.clearProperty("debug")

520

debug2 <- isDebugMode

521

} yield assertTrue(debug1 && !debug2)

522

}

523

524

// Test cross-platform line separator handling

525

test("text file generation") {

526

def generateTextFile(lines: List[String]): ZIO[System, Nothing, String] =

527

for {

528

separator <- System.lineSeparator

529

} yield lines.mkString(separator)

530

531

for {

532

result <- generateTextFile(List("line1", "line2", "line3"))

533

} yield assertTrue(result.contains("line1") && result.contains("line2"))

534

}

535

536

case class AppConfig(host: String, port: Int)

537

```

538

539

### Service Access Functions

540

541

Convenience functions for accessing test services from the test environment.

542

543

```scala { .api }

544

/**

545

* Access TestClock service

546

* @return effect producing TestClock instance

547

*/

548

def testClock: URIO[TestClock, TestClock]

549

550

/**

551

* Access TestClock and apply function

552

* @param f function to apply to TestClock

553

* @return effect with TestClock applied to function

554

*/

555

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

556

557

/**

558

* Access TestConsole service

559

* @return effect producing TestConsole instance

560

*/

561

def testConsole: URIO[TestConsole, TestConsole]

562

563

/**

564

* Access TestConsole and apply function

565

* @param f function to apply to TestConsole

566

* @return effect with TestConsole applied to function

567

*/

568

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

569

570

/**

571

* Access TestRandom service

572

* @return effect producing TestRandom instance

573

*/

574

def testRandom: URIO[TestRandom, TestRandom]

575

576

/**

577

* Access TestRandom and apply function

578

* @param f function to apply to TestRandom

579

* @return effect with TestRandom applied to function

580

*/

581

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

582

583

/**

584

* Access TestSystem service

585

* @return effect producing TestSystem instance

586

*/

587

def testSystem: URIO[TestSystem, TestSystem]

588

589

/**

590

* Access TestSystem and apply function

591

* @param f function to apply to TestSystem

592

* @return effect with TestSystem applied to function

593

*/

594

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

595

```

596

597

**Usage Examples:**

598

599

```scala

600

import zio.test._

601

import zio._

602

603

// Direct service access

604

test("service access patterns") {

605

for {

606

clock <- testClock

607

_ <- clock.adjust(1.hour)

608

console <- testConsole

609

_ <- console.feedLines("test input")

610

random <- testRandom

611

_ <- random.setSeed(123L)

612

system <- testSystem

613

_ <- system.putEnv("TEST_VAR", "test_value")

614

} yield assertTrue(true)

615

}

616

617

// Service access with functions

618

test("service access with functions") {

619

for {

620

_ <- testClockWith(_.adjust(2.hours))

621

output <- testConsoleWith { console =>

622

Console.printLine("Hello") *> console.output

623

}

624

randomValue <- testRandomWith { random =>

625

random.setSeed(456L) *> Random.nextInt(100)

626

}

627

envVar <- testSystemWith { system =>

628

system.putEnv("VAR", "value") *> System.env("VAR")

629

}

630

} yield assertTrue(

631

output.contains("Hello") &&

632

randomValue >= 0 && randomValue < 100 &&

633

envVar.contains("value")

634

)

635

}

636

```

637

638

### Live Service Integration

639

640

Working with live services when test services are insufficient.

641

642

```scala { .api }

643

/**

644

* Execute effect with live clock instead of test clock

645

* @param zio effect to execute with live clock

646

* @return effect executed with live clock service

647

*/

648

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

649

650

/**

651

* Execute effect with live services replacing test services

652

* @param zio effect to execute with live services

653

* @return effect executed with live service implementations

654

*/

655

def withLive[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A]

656

```

657

658

**Usage Examples:**

659

660

```scala

661

import zio.test._

662

import zio._

663

664

// Mix test and live services

665

test("performance measurement with real time") {

666

for {

667

// Use test clock for most of the test

668

testClock <- ZIO.service[TestClock]

669

_ <- testClock.setTime(Duration.fromMillis(1000000L))

670

671

// Use live clock for actual timing measurement

672

start <- live(Clock.currentTime(TimeUnit.MILLISECONDS))

673

_ <- live(ZIO.sleep(100.millis)) // Actually sleep for 100ms

674

end <- live(Clock.currentTime(TimeUnit.MILLISECONDS))

675

676

duration = end - start

677

} yield assertTrue(duration >= 100L && duration < 200L)

678

}

679

680

// Test with real console for debugging

681

test("debug with real console") {

682

for {

683

_ <- Console.printLine("This goes to test console")

684

_ <- live(Console.printLine("This goes to real console for debugging"))

685

testConsole <- ZIO.service[TestConsole]

686

output <- testConsole.output

687

} yield assertTrue(output.size == 1) // Only test console output is captured

688

}

689

```