or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collection-inspectors.mdcore-matchers.mddata-driven-testing.mderror-handling.mdindex.md

error-handling.mddocs/

0

# Error Handling & Context

1

2

The error handling system provides sophisticated error collection, context management, and reporting capabilities. It includes clue systems for enhanced debugging, configurable error collection modes, and comprehensive failure creation utilities.

3

4

## Capabilities

5

6

### Error Collection Interface

7

8

Central interface for managing assertion errors during test execution.

9

10

```kotlin { .api }

11

/**

12

* Interface for collecting and managing assertion errors

13

*/

14

interface ErrorCollector {

15

/** Current nesting depth for error context */

16

var depth: Int

17

18

/** Current test subject for error context */

19

var subject: Printed?

20

21

/**

22

* Get the current error collection mode

23

* @return Current ErrorCollectionMode

24

*/

25

fun getCollectionMode(): ErrorCollectionMode

26

27

/**

28

* Set the error collection mode

29

* @param mode New ErrorCollectionMode to use

30

*/

31

fun setCollectionMode(mode: ErrorCollectionMode)

32

33

/**

34

* Get all collected errors

35

* @return List of collected Throwable instances

36

*/

37

fun errors(): List<Throwable>

38

39

/**

40

* Add an error to the collection

41

* @param t Throwable to add

42

*/

43

fun pushError(t: Throwable)

44

45

/**

46

* Clear all collected errors

47

*/

48

fun clear()

49

50

/**

51

* Add a contextual clue to the error context stack

52

* @param clue Clue to add for error context

53

*/

54

fun pushClue(clue: Clue)

55

56

/**

57

* Remove the most recent clue from the context stack

58

*/

59

fun popClue()

60

61

/**

62

* Get the current clue context stack

63

* @return List of active clues

64

*/

65

fun clueContext(): List<Clue>

66

}

67

68

/**

69

* Error collection behavior modes

70

*/

71

enum class ErrorCollectionMode {

72

/** Collect errors without immediately throwing */

73

Soft,

74

/** Throw errors immediately when they occur */

75

Hard

76

}

77

```

78

79

### Assertion Counter Interface

80

81

Interface for tracking the number of assertions executed during testing.

82

83

```kotlin { .api }

84

/**

85

* Interface for counting assertions during test execution

86

*/

87

interface AssertionCounter {

88

/**

89

* Increment the assertion count

90

*/

91

fun inc()

92

93

/**

94

* Get the current assertion count

95

* @return Current count as Int

96

*/

97

fun get(): Int

98

99

/**

100

* Reset the assertion count to zero

101

*/

102

fun reset()

103

}

104

```

105

106

### Error Creation Functions

107

108

Utility functions for creating and throwing assertion errors.

109

110

```kotlin { .api }

111

/**

112

* Create an AssertionError with the given message

113

* @param message Error message

114

* @return AssertionError instance

115

*/

116

fun failure(message: String): AssertionError

117

118

/**

119

* Create an AssertionError with message and cause

120

* @param message Error message

121

* @param cause Underlying cause throwable

122

* @return AssertionError instance with cause

123

*/

124

fun failure(message: String, cause: Throwable?): AssertionError

125

126

/**

127

* Create a formatted failure with expected/actual values

128

* @param expected Expected value wrapper

129

* @param actual Actual value wrapper

130

* @param prependMessage Optional message to prepend

131

* @return Formatted Throwable

132

*/

133

fun failure(expected: Expected, actual: Actual, prependMessage: String = ""): Throwable

134

135

/**

136

* Immediately throw an assertion failure with the given message

137

* @param msg Error message

138

* @return Nothing (function never returns)

139

*/

140

fun fail(msg: String): Nothing

141

```

142

143

### Exception Testing Functions

144

145

Functions for asserting that code blocks throw or don't throw expected exceptions.

146

147

```kotlin { .api }

148

/**

149

* Assert that the given block throws any exception

150

* @param block Code block that should throw

151

* @return The AssertionError that was thrown

152

*/

153

inline fun shouldFail(block: () -> Any?): AssertionError

154

155

/**

156

* Assert that the given block throws an exception with a specific message

157

* @param message Expected error message

158

* @param block Code block that should throw

159

* @return The AssertionError that was thrown

160

*/

161

inline fun shouldFailWithMessage(message: String, block: () -> Any?): AssertionError

162

163

/**

164

* Assert that the given block throws an exception of type T

165

* @param T The expected exception type

166

* @param block Code block that should throw T

167

* @return The exception of type T that was thrown

168

*/

169

inline fun <reified T : Throwable> shouldThrow(block: () -> Any?): T

170

171

/**

172

* Assert that the given block throws exactly type T (not subclasses)

173

* @param T The expected exception type

174

* @param block Code block that should throw exactly T

175

* @return The exception of type T that was thrown

176

*/

177

inline fun <reified T : Throwable> shouldThrowExactly(block: () -> Any?): T

178

179

/**

180

* Assert that the given block throws type T, returning Unit

181

* @param T The expected exception type

182

* @param block Code block that should throw T

183

*/

184

inline fun <reified T : Throwable> shouldThrowUnit(block: () -> Any?)

185

186

/**

187

* Assert that the given block throws exactly type T, returning Unit

188

* @param T The expected exception type

189

* @param block Code block that should throw exactly T

190

*/

191

inline fun <reified T : Throwable> shouldThrowExactlyUnit(block: () -> Any?)

192

193

/**

194

* Assert that the given block throws any exception

195

* @param block Code block that should throw

196

* @return The Throwable that was thrown

197

*/

198

inline fun shouldThrowAny(block: () -> Any?): Throwable

199

200

/**

201

* Assert that the given block throws T with a specific message

202

* @param T The expected exception type

203

* @param message Expected error message

204

* @param block Code block that should throw T with message

205

* @return The exception of type T that was thrown

206

*/

207

inline fun <reified T : Throwable> shouldThrowWithMessage(message: String, block: () -> Any?): T

208

209

/**

210

* Assert that the given block throws T with a specific message, returning Unit

211

* @param T The expected exception type

212

* @param message Expected error message

213

* @param block Code block that should throw T with message

214

*/

215

inline fun <reified T : Throwable> shouldThrowUnitWithMessage(message: String, block: () -> Any?)

216

217

/**

218

* Assert that the given block throws any exception with a specific message

219

* @param message Expected error message

220

* @param block Code block that should throw with message

221

* @return The Throwable that was thrown

222

*/

223

inline fun shouldThrowMessage(message: String, block: () -> Any?): Throwable

224

225

/**

226

* Assert that the given block does NOT throw type T

227

* @param T The exception type that should not be thrown

228

* @param block Code block that should not throw T

229

* @return The result of executing the block

230

*/

231

inline fun <reified T : Throwable, R> shouldNotThrow(block: () -> R): R

232

233

/**

234

* Assert that the given block does NOT throw exactly type T

235

* @param T The exception type that should not be thrown

236

* @param block Code block that should not throw exactly T

237

* @return The result of executing the block

238

*/

239

inline fun <reified T : Throwable, R> shouldNotThrowExactly(block: () -> R): R

240

241

/**

242

* Assert that the given block does NOT throw type T, returning Unit

243

* @param T The exception type that should not be thrown

244

* @param block Code block that should not throw T

245

*/

246

inline fun <reified T : Throwable> shouldNotThrowUnit(block: () -> Any?)

247

248

/**

249

* Assert that the given block does NOT throw exactly type T, returning Unit

250

* @param T The exception type that should not be thrown

251

* @param block Code block that should not throw exactly T

252

*/

253

inline fun <reified T : Throwable> shouldNotThrowExactlyUnit(block: () -> Any?)

254

255

/**

256

* Assert that the given block does NOT throw any exception

257

* @param block Code block that should not throw

258

* @return The result of executing the block

259

*/

260

inline fun <R> shouldNotThrowAny(block: () -> R): R

261

262

/**

263

* Assert that the given block does NOT throw with a specific message

264

* @param message Error message that should not occur

265

* @param block Code block that should not throw with this message

266

* @return The result of executing the block

267

*/

268

inline fun <R> shouldNotThrowMessage(message: String, block: () -> R): R

269

```

270

271

### Clue System Functions

272

273

Functions for adding contextual information to improve error messages.

274

275

```kotlin { .api }

276

/**

277

* Execute code with additional contextual information for error reporting

278

* @param clue Contextual information to include in error messages

279

* @param thunk Code block to execute with the clue context

280

* @return Result of executing the thunk

281

*/

282

inline fun <R> withClue(clue: Any?, thunk: () -> R): R

283

284

/**

285

* Execute code with lazily-evaluated contextual information

286

* @param clue Function that provides contextual information when needed

287

* @param thunk Code block to execute with the clue context

288

* @return Result of executing the thunk

289

*/

290

inline fun <R> withClue(crossinline clue: () -> Any?, thunk: () -> R): R

291

292

/**

293

* Use this value as contextual information for the given block

294

* @param block Code block to execute with this value as context

295

* @return Result of executing the block

296

*/

297

inline fun <T : Any?, R> T.asClue(block: (T) -> R): R

298

```

299

300

### Global Configuration

301

302

Global configuration object for assertion behavior.

303

304

```kotlin { .api }

305

/**

306

* Global configuration object for assertion behavior

307

*/

308

object AssertionsConfig {

309

/** Whether to show detailed diffs for data classes */

310

val showDataClassDiff: Boolean

311

312

/** Minimum string length before showing diff output */

313

val largeStringDiffMinSize: Int

314

315

/** Format string for multi-line diffs */

316

val multiLineDiff: String

317

318

/** Maximum number of errors to output before truncating */

319

val maxErrorsOutput: Int

320

321

/** Maximum size for map diff output */

322

val mapDiffLimit: Int

323

324

/** Maximum collection size for enumeration in error messages */

325

val maxCollectionEnumerateSize: Int

326

327

/** Whether to disable NaN equality checking */

328

val disableNaNEquality: Boolean

329

330

/** Maximum collection size for printing in error messages */

331

val maxCollectionPrintSize: ConfigValue<Int>

332

}

333

334

/**

335

* Interface for configuration values with metadata about their source

336

*/

337

interface ConfigValue<T> {

338

/** Description of where this configuration value was loaded from */

339

val sourceDescription: String?

340

/** The actual configuration value */

341

val value: T

342

}

343

344

/**

345

* Environment-based configuration value implementation

346

*/

347

class EnvironmentConfigValue<T>(

348

private val name: String,

349

private val defaultValue: T,

350

val converter: (String) -> T

351

) : ConfigValue<T>

352

```

353

354

## Usage Examples

355

356

### Basic Error Creation

357

358

```kotlin

359

import io.kotest.assertions.*

360

import io.kotest.matchers.shouldBe

361

362

// Create and throw assertion errors

363

fun validateAge(age: Int) {

364

if (age < 0) {

365

throw failure("Age cannot be negative: $age")

366

}

367

if (age > 150) {

368

throw failure("Age seems unrealistic: $age")

369

}

370

}

371

372

// Immediate failure

373

fun processUser(user: User?) {

374

user ?: fail("User cannot be null")

375

// Process user...

376

}

377

```

378

379

### Exception Testing Examples

380

381

```kotlin

382

import io.kotest.assertions.*

383

import io.kotest.matchers.shouldBe

384

385

// Assert that code throws any exception

386

val error = shouldFail {

387

divide(10, 0) // Should throw ArithmeticException

388

}

389

390

// Assert specific exception type

391

val arithmeticError = shouldThrow<ArithmeticException> {

392

divide(10, 0)

393

}

394

arithmeticError.message shouldBe "Division by zero"

395

396

// Assert exact exception type (not subclasses)

397

shouldThrowExactly<IllegalArgumentException> {

398

validateInput("")

399

}

400

401

// Assert exception with specific message

402

shouldThrowWithMessage<IllegalArgumentException>("Input cannot be empty") {

403

validateInput("")

404

}

405

406

// Assert any exception with specific message

407

shouldThrowMessage("Invalid operation") {

408

performInvalidOperation()

409

}

410

411

// Assert that code does NOT throw specific exception

412

val result = shouldNotThrow<NullPointerException> {

413

safeOperation() // Should complete successfully

414

}

415

416

// Assert that code does NOT throw any exception

417

val data = shouldNotThrowAny {

418

loadData() // Should complete without throwing

419

}

420

421

// Unit-returning variants (when you don't need the exception/result)

422

shouldThrowUnit<IllegalStateException> {

423

invalidStateOperation()

424

}

425

426

shouldNotThrowUnit<IOException> {

427

fileOperation()

428

}

429

```

430

431

### Using Clues for Better Error Messages

432

433

```kotlin

434

import io.kotest.assertions.*

435

import io.kotest.matchers.shouldBe

436

437

data class User(val name: String, val age: Int, val email: String)

438

439

// Basic clue usage

440

val user = User("Alice", 25, "alice@example.com")

441

442

withClue("Validating user: $user") {

443

user.name shouldBe "Alice"

444

user.age shouldBe 25

445

user.email shouldBe "alice@example.com"

446

}

447

448

// If any assertion fails, the error message will include:

449

// "Validating user: User(name=Alice, age=25, email=alice@example.com)"

450

```

451

452

### Lazy Clues

453

454

```kotlin

455

import io.kotest.assertions.*

456

import io.kotest.matchers.shouldBe

457

458

// Expensive clue computation only happens on failure

459

withClue({ "Current system state: ${getExpensiveSystemState()}" }) {

460

performComplexOperation() shouldBe expectedResult

461

}

462

463

// getExpensiveSystemState() is only called if the assertion fails

464

```

465

466

### Using asClue Extension

467

468

```kotlin

469

import io.kotest.assertions.*

470

import io.kotest.matchers.shouldBe

471

472

data class DatabaseRecord(val id: Int, val data: String)

473

474

val record = DatabaseRecord(123, "important data")

475

476

// Use the record itself as context

477

record.asClue { rec ->

478

rec.id should beGreaterThan(0)

479

rec.data.isNotEmpty() shouldBe true

480

}

481

482

// On failure, includes: "DatabaseRecord(id=123, data=important data)"

483

```

484

485

### Nested Clues

486

487

```kotlin

488

import io.kotest.assertions.*

489

import io.kotest.matchers.shouldBe

490

491

val users = listOf(

492

User("Alice", 25, "alice@example.com"),

493

User("Bob", 30, "bob@example.com")

494

)

495

496

withClue("Processing user list") {

497

users.forEachIndexed { index, user ->

498

withClue("User at index $index") {

499

user.asClue { u ->

500

u.name.isNotEmpty() shouldBe true

501

u.age should beGreaterThan(0)

502

u.email should contain("@")

503

}

504

}

505

}

506

}

507

508

// Nested clues provide hierarchical context in error messages

509

```

510

511

### Error Collection Modes

512

513

```kotlin

514

import io.kotest.assertions.*

515

516

// Example of using error collection (implementation-dependent)

517

fun validateMultipleUsers(users: List<User>) {

518

// Assuming we have access to an ErrorCollector instance

519

val collector = getErrorCollector()

520

521

// Set to soft mode to collect all errors

522

collector.setCollectionMode(ErrorCollectionMode.Soft)

523

524

users.forEach { user ->

525

try {

526

validateUser(user)

527

} catch (e: AssertionError) {

528

collector.pushError(e)

529

}

530

}

531

532

// Check if any errors were collected

533

val errors = collector.errors()

534

if (errors.isNotEmpty()) {

535

throw failure("Multiple validation errors: ${errors.size} users failed validation")

536

}

537

}

538

```

539

540

### Complex Error Context

541

542

```kotlin

543

import io.kotest.assertions.*

544

import io.kotest.matchers.shouldBe

545

546

data class TestScenario(val name: String, val input: Any, val expected: Any)

547

548

fun runTestScenarios(scenarios: List<TestScenario>) {

549

scenarios.forEach { scenario ->

550

withClue("Test scenario: ${scenario.name}") {

551

scenario.input.asClue { input ->

552

withClue("Expected: ${scenario.expected}") {

553

val result = processInput(input)

554

result shouldBe scenario.expected

555

}

556

}

557

}

558

}

559

}

560

561

// Provides rich context like:

562

// "Test scenario: Valid email processing"

563

// "Input: test@example.com"

564

// "Expected: true"

565

// "but was: false"

566

```

567

568

### Custom Error Messages with Context

569

570

```kotlin

571

import io.kotest.assertions.*

572

import io.kotest.matchers.shouldBe

573

574

fun validateApiResponse(response: ApiResponse) {

575

response.asClue { resp ->

576

withClue("Response validation") {

577

resp.status shouldBe 200

578

resp.data.shouldNotBeNull()

579

580

withClue("Response timing validation") {

581

resp.responseTime should beLessThan(1000)

582

}

583

584

withClue("Response content validation") {

585

resp.data.asClue { data ->

586

data.size should beGreaterThan(0)

587

data.forAll { item ->

588

item.id should beGreaterThan(0)

589

}

590

}

591

}

592

}

593

}

594

}

595

```

596

597

## Integration with Other Systems

598

599

### Print System Integration

600

601

The error handling system integrates with the print system for formatting values in error messages:

602

603

```kotlin

604

import io.kotest.assertions.*

605

import io.kotest.assertions.print.*

606

607

// Custom printed values appear in error messages

608

data class CustomData(val value: String)

609

610

// Error messages will use the print system to format CustomData instances

611

val data = CustomData("test")

612

data shouldBe CustomData("other") // Uses print system for clear error output

613

```

614

615

### Matcher Integration

616

617

Error handling works seamlessly with the matcher system:

618

619

```kotlin

620

import io.kotest.assertions.*

621

import io.kotest.matchers.*

622

623

// Matcher failures automatically use the error handling system

624

"hello" should startWith("hi") // Uses failure() internally for error creation

625

```

626

627

## Best Practices

628

629

1. **Use Clues Liberally**: Add context to make debugging easier

630

2. **Prefer Specific Messages**: Include relevant data in error messages

631

3. **Lazy Clues for Expensive Operations**: Use clue functions for expensive computations

632

4. **Nested Context**: Build hierarchical context with nested clues

633

5. **Object Context**: Use `asClue` to include object state in errors

634

6. **Early Validation**: Use `fail()` for immediate termination on invalid conditions