0
# Exception and Failure Testing
1
2
Assertions for testing exception handling, failure scenarios, and expected error conditions. Essential for robust testing of error paths and edge cases.
3
4
## Capabilities
5
6
### Failure Assertions
7
8
#### fail Function
9
10
Marks a test as failed immediately with an optional message and cause.
11
12
```kotlin { .api }
13
/**
14
* Marks a test as having failed if this point in the execution path is reached
15
* @param message - Optional failure message
16
* @return Nothing (function never returns)
17
*/
18
fun fail(message: String? = null): Nothing
19
20
/**
21
* Marks a test as having failed with an optional message and cause exception
22
* @param message - Optional failure message
23
* @param cause - Optional exception that caused the failure
24
* @return Nothing (function never returns)
25
*/
26
fun fail(message: String? = null, cause: Throwable? = null): Nothing
27
```
28
29
**Usage Examples:**
30
31
```kotlin
32
import kotlin.test.*
33
34
@Test
35
fun testConditionalFailure() {
36
val result = performCriticalOperation()
37
38
when (result.status) {
39
Status.SUCCESS -> {
40
// Test passes, continue validation
41
assertTrue(result.data.isNotEmpty())
42
}
43
Status.RECOVERABLE_ERROR -> {
44
// Expected failure scenario
45
assertTrue(result.canRetry)
46
}
47
Status.FATAL_ERROR -> {
48
fail("Critical operation failed: ${result.errorMessage}")
49
}
50
else -> {
51
fail("Unexpected status: ${result.status}")
52
}
53
}
54
}
55
56
@Test
57
fun testWithCause() {
58
try {
59
val connection = establishConnection()
60
if (connection == null) {
61
fail("Failed to establish connection")
62
}
63
} catch (e: NetworkException) {
64
fail("Network error during connection", e)
65
}
66
}
67
```
68
69
### Exception Assertions
70
71
#### assertFails Function
72
73
Asserts that a block of code throws any exception.
74
75
```kotlin { .api }
76
/**
77
* Asserts that given function block fails by throwing an exception
78
* @param block - Code block that should throw an exception
79
* @return The exception that was thrown
80
*/
81
inline fun assertFails(block: () -> Unit): Throwable
82
83
/**
84
* Asserts that given function block fails by throwing an exception
85
* @param message - Optional message used as prefix for failure message
86
* @param block - Code block that should throw an exception
87
* @return The exception that was thrown
88
*/
89
inline fun assertFails(message: String?, block: () -> Unit): Throwable
90
```
91
92
**Usage Examples:**
93
94
```kotlin
95
@Test
96
fun testExceptionHandling() {
97
// Test that division by zero throws
98
val exception = assertFails {
99
val result = 10 / 0
100
}
101
assertTrue(exception is ArithmeticException)
102
103
// Test with custom message
104
val parseException = assertFails("Should fail to parse invalid number") {
105
"not-a-number".toInt()
106
}
107
assertTrue(parseException is NumberFormatException)
108
109
// Inspect exception details
110
val validationException = assertFails {
111
validateEmail("invalid-email")
112
}
113
assertContains(validationException.message ?: "", "email")
114
}
115
116
@Test
117
fun testApiErrorHandling() {
118
val client = ApiClient()
119
120
// Test unauthorized access
121
val authException = assertFails("Should fail with invalid token") {
122
client.makeRequest("/protected", token = "invalid")
123
}
124
125
// Verify we can inspect the returned exception
126
assertTrue(authException.message?.contains("unauthorized") == true)
127
}
128
```
129
130
#### assertFailsWith Function
131
132
Asserts that a block of code throws a specific type of exception.
133
134
```kotlin { .api }
135
/**
136
* Asserts that a block fails with a specific exception of type T
137
* @param message - Optional message used as prefix for failure message
138
* @param block - Code block that should throw exception of type T
139
* @return Exception of the expected type T
140
*/
141
inline fun <reified T : Throwable> assertFailsWith(message: String? = null, block: () -> Unit): T
142
143
/**
144
* Asserts that a block fails with a specific exception of the given class
145
* @param exceptionClass - Expected exception class
146
* @param block - Code block that should throw the exception
147
* @return Exception of the expected type T
148
*/
149
inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, block: () -> Unit): T
150
151
/**
152
* Asserts that a block fails with a specific exception of the given class
153
* @param exceptionClass - Expected exception class
154
* @param message - Optional message used as prefix for failure message
155
* @param block - Code block that should throw the exception
156
* @return Exception of the expected type T
157
*/
158
inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, message: String?, block: () -> Unit): T
159
```
160
161
**Usage Examples:**
162
163
```kotlin
164
@Test
165
fun testSpecificExceptions() {
166
// Test specific exception type with reified generic
167
val illegalArg = assertFailsWith<IllegalArgumentException> {
168
validateAge(-5)
169
}
170
assertEquals("Age cannot be negative", illegalArg.message)
171
172
// Test with custom message
173
val nullPointer = assertFailsWith<NullPointerException>("Should throw NPE for null input") {
174
processData(null)
175
}
176
177
// Test with KClass parameter
178
val ioException = assertFailsWith(IOException::class) {
179
readFile("nonexistent.txt")
180
}
181
assertContains(ioException.message ?: "", "nonexistent.txt")
182
}
183
184
@Test
185
fun testCustomExceptions() {
186
class ValidationException(message: String) : Exception(message)
187
188
val validationError = assertFailsWith<ValidationException> {
189
validateUserInput(UserInput(name = "", email = "invalid"))
190
}
191
192
assertContains(validationError.message ?: "", "name")
193
assertContains(validationError.message ?: "", "email")
194
}
195
196
@Test
197
fun testExceptionHierarchy() {
198
// Test that we catch the specific subtype
199
val specificException = assertFailsWith<FileNotFoundException> {
200
openFile("missing.txt")
201
}
202
203
// FileNotFoundException is also an IOException
204
assertTrue(specificException is IOException)
205
}
206
```
207
208
### Expected Value Testing
209
210
#### expect Function
211
212
Asserts that a block returns the expected value (convenience function combining execution and assertion).
213
214
```kotlin { .api }
215
/**
216
* Asserts that given function block returns the given expected value
217
* @param expected - Expected return value
218
* @param block - Function that should return the expected value
219
*/
220
inline fun <T> expect(expected: T, block: () -> T)
221
222
/**
223
* Asserts that given function block returns the given expected value with custom message
224
* @param expected - Expected return value
225
* @param message - Optional custom failure message
226
* @param block - Function that should return the expected value
227
*/
228
inline fun <T> expect(expected: T, message: String?, block: () -> T)
229
```
230
231
**Usage Examples:**
232
233
```kotlin
234
@Test
235
fun testExpectedValues() {
236
// Simple calculation
237
expect(25) { 5 * 5 }
238
239
// String operations
240
expect("HELLO") { "hello".uppercase() }
241
242
// With custom message
243
expect(true, "User should be authenticated") {
244
authenticateUser("admin", "password123")
245
}
246
247
// Complex operations
248
expect(listOf(2, 4, 6)) {
249
listOf(1, 2, 3).map { it * 2 }
250
}
251
}
252
253
@Test
254
fun testFunctionResults() {
255
// Test pure functions
256
expect(42) { fibonacci(9) } // 9th Fibonacci number
257
258
// Test with side effects
259
val cache = mutableMapOf<String, Int>()
260
expect(5) {
261
cache.computeIfAbsent("key") { 5 }
262
}
263
264
// Verify side effect occurred
265
assertTrue(cache.containsKey("key"))
266
}
267
```
268
269
## Advanced Exception Testing Patterns
270
271
### Testing Exception Chains
272
273
```kotlin
274
@Test
275
fun testExceptionChaining() {
276
class DatabaseException(message: String, cause: Throwable) : Exception(message, cause)
277
class ServiceException(message: String, cause: Throwable) : Exception(message, cause)
278
279
val serviceException = assertFailsWith<ServiceException> {
280
try {
281
// Simulate database failure
282
throw SQLException("Connection timeout")
283
} catch (e: SQLException) {
284
throw DatabaseException("Database operation failed", e)
285
} catch (e: DatabaseException) {
286
throw ServiceException("Service unavailable", e)
287
}
288
}
289
290
// Verify exception chain
291
assertNotNull(serviceException.cause)
292
assertTrue(serviceException.cause is DatabaseException)
293
294
val dbException = serviceException.cause as DatabaseException
295
assertNotNull(dbException.cause)
296
assertTrue(dbException.cause is SQLException)
297
298
val sqlException = dbException.cause as SQLException
299
assertEquals("Connection timeout", sqlException.message)
300
}
301
```
302
303
### Testing Resource Management
304
305
```kotlin
306
@Test
307
fun testResourceCleanup() {
308
class Resource : AutoCloseable {
309
var closed = false
310
override fun close() { closed = true }
311
}
312
313
val resource = Resource()
314
315
// Test that exception doesn't prevent cleanup
316
assertFailsWith<IllegalStateException> {
317
try {
318
resource.use {
319
throw IllegalStateException("Simulated error")
320
}
321
} finally {
322
assertTrue(resource.closed, "Resource should be closed even after exception")
323
}
324
}
325
}
326
```
327
328
### Testing Validation Logic
329
330
```kotlin
331
@Test
332
fun testInputValidation() {
333
data class User(val name: String, val email: String, val age: Int)
334
335
fun validateUser(user: User) {
336
require(user.name.isNotBlank()) { "Name cannot be blank" }
337
require(user.email.contains("@")) { "Invalid email format" }
338
require(user.age >= 0) { "Age cannot be negative" }
339
}
340
341
// Test each validation rule
342
assertFailsWith<IllegalArgumentException>("Should reject blank name") {
343
validateUser(User("", "test@example.com", 25))
344
}
345
346
assertFailsWith<IllegalArgumentException>("Should reject invalid email") {
347
validateUser(User("John", "invalid-email", 25))
348
}
349
350
assertFailsWith<IllegalArgumentException>("Should reject negative age") {
351
validateUser(User("John", "john@example.com", -1))
352
}
353
354
// Test valid input doesn't throw
355
assertDoesNotThrow {
356
validateUser(User("John", "john@example.com", 25))
357
}
358
}
359
360
// Helper function for readability
361
inline fun assertDoesNotThrow(block: () -> Unit) {
362
try {
363
block()
364
} catch (e: Exception) {
365
fail("Expected no exception, but got: ${e::class.simpleName}: ${e.message}")
366
}
367
}
368
```
369
370
### Testing Timeout and Async Operations
371
372
```kotlin
373
@Test
374
fun testTimeoutBehavior() {
375
// Test that long-running operation times out
376
val timeoutException = assertFailsWith<TimeoutException> {
377
runWithTimeout(100) { // 100ms timeout
378
Thread.sleep(1000) // Sleep for 1 second
379
}
380
}
381
382
assertContains(timeoutException.message ?: "", "timeout")
383
}
384
385
@Test
386
fun testAsyncExceptions() {
387
val future = CompletableFuture<String>()
388
389
// Complete with exception
390
future.completeExceptionally(RuntimeException("Async failure"))
391
392
val executionException = assertFailsWith<ExecutionException> {
393
future.get()
394
}
395
396
assertTrue(executionException.cause is RuntimeException)
397
assertEquals("Async failure", executionException.cause?.message)
398
}
399
```
400
401
### Testing State Transitions
402
403
```kotlin
404
@Test
405
fun testStateMachine() {
406
enum class State { CREATED, STARTED, STOPPED, DISPOSED }
407
408
class StateMachine {
409
private var state = State.CREATED
410
411
fun start() {
412
require(state == State.CREATED) { "Can only start from CREATED state" }
413
state = State.STARTED
414
}
415
416
fun stop() {
417
require(state == State.STARTED) { "Can only stop from STARTED state" }
418
state = State.STOPPED
419
}
420
421
fun dispose() {
422
require(state == State.STOPPED) { "Can only dispose from STOPPED state" }
423
state = State.DISPOSED
424
}
425
}
426
427
val machine = StateMachine()
428
429
// Test invalid transitions
430
assertFailsWith<IllegalArgumentException>("Should not stop before starting") {
431
machine.stop()
432
}
433
434
assertFailsWith<IllegalArgumentException>("Should not dispose before stopping") {
435
machine.dispose()
436
}
437
438
// Test valid sequence
439
machine.start()
440
machine.stop()
441
machine.dispose()
442
443
// Test invalid transition after disposal
444
assertFailsWith<IllegalArgumentException>("Should not restart after disposal") {
445
machine.start()
446
}
447
}
448
```
449
450
### Test Code Utilities
451
452
#### todo Function
453
454
Marks unimplemented test code that should be skipped but kept referenced in the codebase.
455
456
```kotlin { .api }
457
/**
458
* Takes the given block of test code and doesn't execute it
459
* This keeps the code under test referenced, but doesn't actually test it until implemented
460
* @param block - Test code block to skip execution
461
*/
462
fun todo(block: () -> Unit)
463
```
464
465
**Usage Examples:**
466
467
```kotlin
468
import kotlin.test.*
469
470
@Test
471
fun testComplexFeature() {
472
// Test implemented part
473
val basicResult = performBasicOperation()
474
assertEquals("expected", basicResult)
475
476
// Skip unimplemented test but keep it in code
477
todo {
478
// This test code won't execute but stays in the codebase
479
val advancedResult = performAdvancedOperation()
480
assertEquals("advanced", advancedResult)
481
assertNotNull(advancedResult.metadata)
482
}
483
}
484
485
@Test
486
fun testTodoWithDescription() {
487
// Test current implementation
488
val service = createService()
489
assertTrue(service.isReady())
490
491
// TODO: Add comprehensive integration tests
492
todo {
493
// Integration test placeholder - won't run
494
val integrationResult = service.performIntegration()
495
assertTrue(integrationResult.success)
496
assertContains(integrationResult.logs, "Integration completed")
497
}
498
}
499
500
class IncrementalTestDevelopment {
501
@Test
502
fun testBasicFunctionality() {
503
// Implemented test
504
val calculator = Calculator()
505
assertEquals(4, calculator.add(2, 2))
506
}
507
508
@Test
509
fun testAdvancedFunctionality() {
510
todo {
511
// Placeholder for future advanced tests
512
val calculator = Calculator()
513
assertEquals(8.0, calculator.power(2.0, 3.0), 0.001)
514
assertEquals(2.0, calculator.sqrt(4.0), 0.001)
515
}
516
}
517
}
518
```
519
520
**Platform Behavior:**
521
- **JVM**: Prints "TODO at [stack trace location]" to console
522
- **JavaScript**: Prints "TODO at [function reference]" to console
523
- **Native/WASM**: Prints "TODO at [function reference]" to console
524
525
The `todo` function is particularly useful during test-driven development when you want to outline test cases but implement them incrementally, ensuring unimplemented tests don't cause failures while keeping them visible in the codebase.
526
527
## Types
528
529
```kotlin { .api }
530
/**
531
* Represents a Kotlin class for type-safe exception assertions
532
*/
533
typealias KClass<T> = kotlin.reflect.KClass<T>
534
```