0
# Exception Testing
1
2
Exception assertion functions for testing error conditions with support for specific exception types and cause chains. These functions are essential for validating that code properly handles error cases and throws appropriate exceptions.
3
4
## Capabilities
5
6
### assertFails
7
8
Asserts that a code block throws any exception and returns the thrown exception.
9
10
```kotlin { .api }
11
/**
12
* Asserts that the given block fails by throwing an exception.
13
* @param block The code block that should throw an exception
14
* @return The thrown exception
15
* @throws AssertionError if the block completes without throwing
16
*/
17
fun assertFails(block: () -> Unit): Throwable
18
19
/**
20
* Asserts that the given block fails by throwing an exception.
21
* @param message Optional message to show if assertion fails
22
* @param block The code block that should throw an exception
23
* @return The thrown exception
24
* @throws AssertionError if the block completes without throwing
25
* @since Kotlin 1.1
26
*/
27
fun assertFails(message: String?, block: () -> Unit): Throwable
28
```
29
30
**Usage Examples:**
31
32
```kotlin
33
import kotlin.test.assertFails
34
import kotlin.test.assertEquals
35
36
@Test
37
fun testGeneralExceptionHandling() {
38
// Basic exception testing - any exception type
39
val exception = assertFails("Should throw when dividing by zero") {
40
val result = 10 / 0
41
}
42
assertTrue(exception is ArithmeticException)
43
44
// Testing API error responses
45
val apiException = assertFails {
46
apiClient.fetchUser("invalid-id")
47
}
48
assertContains(apiException.message ?: "", "invalid")
49
50
// Testing validation logic
51
assertFails("Should fail with invalid email") {
52
User.create(name = "John", email = "invalid-email")
53
}
54
55
// Testing concurrent operations
56
val concurrencyException = assertFails {
57
unsafeMap.putAll(generateLargeMap())
58
}
59
// Can examine the exception further
60
println("Caught exception: ${concurrencyException.javaClass.simpleName}")
61
}
62
```
63
64
### assertFailsWith (Reified)
65
66
Asserts that a code block throws a specific exception type using reified generics.
67
68
```kotlin { .api }
69
/**
70
* Asserts that the given block fails with a specific exception type.
71
* Uses reified generics for type-safe exception testing.
72
* @param message Optional message to show if assertion fails
73
* @param block The code block that should throw the exception
74
* @return The thrown exception cast to the specified type
75
* @throws AssertionError if block doesn't throw or throws wrong type
76
*/
77
inline fun <reified T : Throwable> assertFailsWith(
78
message: String? = null,
79
block: () -> Unit
80
): T
81
```
82
83
**Usage Examples:**
84
85
```kotlin
86
import kotlin.test.assertFailsWith
87
import kotlin.test.assertEquals
88
89
@Test
90
fun testSpecificExceptionTypes() {
91
// Test specific exception type with smart cast
92
val illegalArg = assertFailsWith<IllegalArgumentException> {
93
validateAge(-5)
94
}
95
assertEquals("Age cannot be negative", illegalArg.message)
96
97
// Test custom exceptions
98
val customException = assertFailsWith<UserNotFoundException>(
99
"Should throw UserNotFoundException for missing user"
100
) {
101
userService.getUser("nonexistent-id")
102
}
103
assertEquals("nonexistent-id", customException.userId)
104
105
// Test standard library exceptions
106
val indexException = assertFailsWith<IndexOutOfBoundsException> {
107
val list = listOf(1, 2, 3)
108
list[10] // Should throw
109
}
110
assertContains(indexException.message ?: "", "10")
111
112
// Test null pointer exceptions
113
val nullException = assertFailsWith<KotlinNullPointerException> {
114
val nullString: String? = null
115
nullString!!.length
116
}
117
118
// Test type cast exceptions
119
val castException = assertFailsWith<ClassCastException> {
120
val any: Any = "string"
121
any as Int
122
}
123
}
124
```
125
126
### assertFailsWith (KClass)
127
128
Asserts that a code block throws a specific exception type using KClass reflection.
129
130
```kotlin { .api }
131
/**
132
* Asserts that the given block fails with a specific exception class.
133
* Uses KClass for runtime type checking.
134
* @param exceptionClass The class of exception expected
135
* @param block The code block that should throw the exception
136
* @return The thrown exception cast to the specified type
137
* @throws AssertionError if block doesn't throw or throws wrong type
138
*/
139
fun <T : Throwable> assertFailsWith(
140
exceptionClass: KClass<T>,
141
block: () -> Unit
142
): T
143
144
/**
145
* Asserts that the given block fails with a specific exception class.
146
* @param exceptionClass The class of exception expected
147
* @param message Optional message to show if assertion fails
148
* @param block The code block that should throw the exception
149
* @return The thrown exception cast to the specified type
150
*/
151
fun <T : Throwable> assertFailsWith(
152
exceptionClass: KClass<T>,
153
message: String?,
154
block: () -> Unit
155
): T
156
```
157
158
**Usage Examples:**
159
160
```kotlin
161
import kotlin.test.assertFailsWith
162
import kotlin.reflect.KClass
163
164
@Test
165
fun testExceptionClassChecking() {
166
// Using KClass directly
167
val exception = assertFailsWith(IllegalStateException::class) {
168
stateMachine.performInvalidTransition()
169
}
170
assertContains(exception.message ?: "", "invalid transition")
171
172
// With custom message
173
val networkException = assertFailsWith(
174
NetworkException::class,
175
"Should throw NetworkException for connection timeout"
176
) {
177
networkClient.connectWithTimeout(timeout = 0)
178
}
179
assertEquals("Connection timeout", networkException.reason)
180
181
// Dynamic exception type testing
182
fun testDynamicException(expectedType: KClass<out Exception>) {
183
assertFailsWith(expectedType) {
184
riskyOperation()
185
}
186
}
187
188
testDynamicException(SecurityException::class)
189
testDynamicException(IllegalAccessException::class)
190
}
191
```
192
193
### Exception Hierarchy Testing
194
195
Test exception inheritance and exception cause chains.
196
197
**Usage Examples:**
198
199
```kotlin
200
import kotlin.test.assertFailsWith
201
import kotlin.test.assertIs
202
203
@Test
204
fun testExceptionHierarchy() {
205
// Test exception inheritance
206
val runtimeException = assertFailsWith<RuntimeException> {
207
// This throws IllegalArgumentException, which extends RuntimeException
208
validateInput("")
209
}
210
// Can check specific subtype
211
assertIs<IllegalArgumentException>(runtimeException)
212
213
// Test exception causes
214
val wrapperException = assertFailsWith<ServiceException> {
215
serviceLayer.performComplexOperation()
216
}
217
218
// Check the cause chain
219
val cause = wrapperException.cause
220
assertNotNull(cause, "Exception should have a cause")
221
assertIs<DatabaseException>(cause)
222
223
val rootCause = cause.cause
224
assertNotNull(rootCause, "Should have root cause")
225
assertIs<SQLException>(rootCause)
226
assertEquals("Connection timeout", rootCause.message)
227
}
228
229
@Test
230
fun testCustomExceptionProperties() {
231
// Test custom exception with additional properties
232
val validationException = assertFailsWith<ValidationException> {
233
validator.validate(invalidData)
234
}
235
236
assertEquals("field1", validationException.fieldName)
237
assertEquals("required", validationException.validationType)
238
assertTrue(validationException.errors.isNotEmpty())
239
240
// Test exception with error codes
241
val apiException = assertFailsWith<ApiException> {
242
apiClient.makeRequest("/invalid-endpoint")
243
}
244
245
assertEquals(404, apiException.statusCode)
246
assertEquals("NOT_FOUND", apiException.errorCode)
247
assertContains(apiException.details, "endpoint")
248
}
249
```
250
251
## Error Handling
252
253
Exception testing functions will throw an `AssertionError` when conditions fail:
254
255
- **assertFails**: Throws when the block completes without throwing any exception
256
- **assertFailsWith**: Throws when the block doesn't throw or throws the wrong exception type
257
258
Error messages provide clear information about the mismatch:
259
260
```kotlin
261
// This will throw: AssertionError: Expected an exception to be thrown, but was completed successfully.
262
assertFails {
263
val result = 2 + 2 // No exception thrown
264
}
265
266
// This will throw: AssertionError: Expected an exception of type IllegalArgumentException, actual was RuntimeException
267
assertFailsWith<IllegalArgumentException> {
268
throw RuntimeException("Wrong type")
269
}
270
```
271
272
## Best Practices
273
274
### Message Validation
275
Always validate exception messages to ensure meaningful error reporting:
276
277
```kotlin
278
val exception = assertFailsWith<ValidationException> {
279
validateEmail("invalid-email")
280
}
281
assertContains(exception.message ?: "", "email")
282
assertContains(exception.message ?: "", "format")
283
```
284
285
### Exception State Testing
286
Test that exceptions contain proper state information:
287
288
```kotlin
289
val businessException = assertFailsWith<InsufficientFundsException> {
290
account.withdraw(1000.0)
291
}
292
assertEquals(account.balance, businessException.availableBalance)
293
assertEquals(1000.0, businessException.requestedAmount)
294
```
295
296
### Cause Chain Validation
297
When testing wrapped exceptions, validate the entire cause chain:
298
299
```kotlin
300
val serviceException = assertFailsWith<ServiceException> {
301
userService.createUser(invalidUserData)
302
}
303
304
// Check immediate cause
305
val validationCause = assertIs<ValidationException>(serviceException.cause)
306
assertEquals("Invalid user data", validationCause.message)
307
308
// Check root cause
309
val fieldCause = assertIs<FieldValidationException>(validationCause.cause)
310
assertEquals("email", fieldCause.fieldName)
311
```