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