0
# Exception Assertions
1
2
Specialized assertions for throwables and exceptions including message and cause chain validation.
3
4
## Capabilities
5
6
### Throwable Subject Creation
7
8
Entry point for creating assertions about exceptions and throwables.
9
10
```java { .api }
11
/**
12
* Creates a ThrowableSubject for asserting about Throwable instances.
13
* @param actual the Throwable under test
14
*/
15
public static ThrowableSubject assertThat(Throwable actual);
16
```
17
18
**Usage Examples:**
19
20
```java
21
try {
22
someMethodThatThrows();
23
fail("Expected exception to be thrown");
24
} catch (Exception e) {
25
assertThat(e).isInstanceOf(IllegalArgumentException.class);
26
}
27
```
28
29
### Exception Message Assertions
30
31
Methods for asserting about exception messages using StringSubject capabilities.
32
33
```java { .api }
34
/**
35
* Returns a StringSubject for making assertions about the exception's message.
36
* The message is obtained via getMessage().
37
*/
38
public StringSubject hasMessageThat();
39
```
40
41
**Usage Examples:**
42
43
```java
44
IllegalArgumentException exception =
45
new IllegalArgumentException("Invalid input: null value not allowed");
46
47
// Basic message assertions
48
assertThat(exception).hasMessageThat().contains("Invalid input");
49
assertThat(exception).hasMessageThat().startsWith("Invalid");
50
assertThat(exception).hasMessageThat().endsWith("not allowed");
51
52
// Pattern matching in messages
53
assertThat(exception).hasMessageThat().matches("Invalid input: .* not allowed");
54
assertThat(exception).hasMessageThat().containsMatch("null value");
55
56
// Case-insensitive message checks
57
assertThat(exception).hasMessageThat().ignoringCase().contains("INVALID");
58
```
59
60
### Exception Cause Assertions
61
62
Methods for asserting about exception causes, enabling deep cause chain validation.
63
64
```java { .api }
65
/**
66
* Returns a ThrowableSubject for making assertions about the exception's cause.
67
* The cause is obtained via getCause().
68
*/
69
public ThrowableSubject hasCauseThat();
70
```
71
72
**Usage Examples:**
73
74
```java
75
// Exception with nested causes
76
IOException ioException = new IOException("File not found");
77
RuntimeException runtimeException = new RuntimeException("Processing failed", ioException);
78
79
// Asserting about direct cause
80
assertThat(runtimeException).hasCauseThat().isInstanceOf(IOException.class);
81
assertThat(runtimeException).hasCauseThat().hasMessageThat().contains("File not found");
82
83
// Chaining cause assertions for deep cause chains
84
DatabaseException dbException = new DatabaseException("Connection failed");
85
ServiceException serviceException = new ServiceException("Service unavailable", dbException);
86
ApplicationException appException = new ApplicationException("Request failed", serviceException);
87
88
assertThat(appException)
89
.hasCauseThat().isInstanceOf(ServiceException.class)
90
.hasCauseThat().isInstanceOf(DatabaseException.class)
91
.hasMessageThat().contains("Connection failed");
92
```
93
94
### Common Exception Testing Patterns
95
96
Real-world patterns for testing exception behavior in applications.
97
98
#### Testing Expected Exceptions
99
100
```java
101
// Method that should throw exception
102
@Test
103
public void testInvalidInput_ThrowsException() {
104
IllegalArgumentException exception = assertThrows(
105
IllegalArgumentException.class,
106
() -> calculator.divide(10, 0)
107
);
108
109
assertThat(exception).hasMessageThat().contains("Division by zero");
110
}
111
112
// Using Truth with assertThrows
113
@Test
114
public void testFileOperation_ThrowsIOException() {
115
IOException exception = assertThrows(
116
IOException.class,
117
() -> fileService.readNonExistentFile("missing.txt")
118
);
119
120
assertThat(exception).hasMessageThat().startsWith("File not found");
121
assertThat(exception).hasCauseThat().isNull();
122
}
123
```
124
125
#### Validation Exception Testing
126
127
```java
128
// Testing validation exceptions with detailed messages
129
@Test
130
public void testUserValidation_InvalidEmail() {
131
ValidationException exception = assertThrows(
132
ValidationException.class,
133
() -> userService.createUser("invalid-email", "password")
134
);
135
136
assertThat(exception).hasMessageThat().contains("email");
137
assertThat(exception).hasMessageThat().matches(".*email.*format.*");
138
assertThat(exception).hasMessageThat().ignoringCase().contains("INVALID");
139
}
140
141
// Testing multiple validation errors
142
@Test
143
public void testUserValidation_MultipleErrors() {
144
ValidationException exception = assertThrows(
145
ValidationException.class,
146
() -> userService.createUser("", "")
147
);
148
149
assertThat(exception).hasMessageThat().contains("email");
150
assertThat(exception).hasMessageThat().contains("password");
151
}
152
```
153
154
#### Exception Chaining Validation
155
156
```java
157
// Testing proper exception wrapping in service layers
158
@Test
159
public void testServiceLayer_WrapsDataAccessExceptions() {
160
// Simulate data access exception
161
when(userRepository.findById(1L)).thenThrow(
162
new DataAccessException("Database connection timeout")
163
);
164
165
ServiceException exception = assertThrows(
166
ServiceException.class,
167
() -> userService.getUser(1L)
168
);
169
170
// Verify exception wrapping
171
assertThat(exception).hasMessageThat().contains("Failed to retrieve user");
172
assertThat(exception).hasCauseThat().isInstanceOf(DataAccessException.class);
173
assertThat(exception).hasCauseThat().hasMessageThat().contains("Database connection timeout");
174
}
175
```
176
177
#### Security Exception Testing
178
179
```java
180
// Testing authentication exceptions
181
@Test
182
public void testAuthentication_InvalidCredentials() {
183
AuthenticationException exception = assertThrows(
184
AuthenticationException.class,
185
() -> authService.authenticate("user", "wrongpassword")
186
);
187
188
assertThat(exception).hasMessageThat().contains("Invalid credentials");
189
assertThat(exception).hasMessageThat().doesNotContain("password"); // Security: don't leak password details
190
}
191
192
// Testing authorization exceptions
193
@Test
194
public void testAuthorization_InsufficientPermissions() {
195
AuthorizationException exception = assertThrows(
196
AuthorizationException.class,
197
() -> adminService.deleteUser(regularUser, targetUserId)
198
);
199
200
assertThat(exception).hasMessageThat().contains("Insufficient permissions");
201
assertThat(exception).hasMessageThat().containsMatch("user.*not authorized");
202
}
203
```
204
205
#### Configuration Exception Testing
206
207
```java
208
// Testing configuration validation
209
@Test
210
public void testConfiguration_MissingProperty() {
211
ConfigurationException exception = assertThrows(
212
ConfigurationException.class,
213
() -> configService.loadConfig("invalid-config.properties")
214
);
215
216
assertThat(exception).hasMessageThat().contains("Missing required property");
217
assertThat(exception).hasMessageThat().matches(".*database\\.url.*required.*");
218
}
219
220
// Testing configuration format errors
221
@Test
222
public void testConfiguration_InvalidFormat() {
223
ConfigurationException exception = assertThrows(
224
ConfigurationException.class,
225
() -> configService.loadConfig("malformed-config.xml")
226
);
227
228
assertThat(exception).hasMessageThat().contains("Invalid XML format");
229
assertThat(exception).hasCauseThat().isInstanceOf(XMLParseException.class);
230
}
231
```
232
233
#### Timeout and Resource Exception Testing
234
235
```java
236
// Testing timeout exceptions
237
@Test
238
public void testNetworkCall_Timeout() {
239
TimeoutException exception = assertThrows(
240
TimeoutException.class,
241
() -> networkService.callExternalApi()
242
);
243
244
assertThat(exception).hasMessageThat().contains("timeout");
245
assertThat(exception).hasMessageThat().containsMatch("\\d+ms"); // Contains timeout duration
246
}
247
248
// Testing resource exhaustion
249
@Test
250
public void testResourceExhaustion_OutOfMemory() {
251
ResourceException exception = assertThrows(
252
ResourceException.class,
253
() -> memoryIntensiveOperation()
254
);
255
256
assertThat(exception).hasMessageThat().contains("Out of memory");
257
assertThat(exception).hasCauseThat().isInstanceOf(OutOfMemoryError.class);
258
}
259
```
260
261
#### Custom Exception Testing
262
263
```java
264
// Testing custom business exceptions
265
@Test
266
public void testBusinessLogic_InvalidBusinessRule() {
267
BusinessRuleException exception = assertThrows(
268
BusinessRuleException.class,
269
() -> orderService.processOrder(invalidOrder)
270
);
271
272
assertThat(exception).hasMessageThat().contains("Business rule violation");
273
assertThat(exception).hasMessageThat().contains(invalidOrder.getId().toString());
274
assertThat(exception.getErrorCode()).isEqualTo("INVALID_ORDER_STATE");
275
}
276
277
// Testing exception with custom properties
278
public class CustomException extends Exception {
279
private final String errorCode;
280
private final int statusCode;
281
282
// constructor and getters...
283
}
284
285
@Test
286
public void testCustomException_Properties() {
287
CustomException exception = assertThrows(
288
CustomException.class,
289
() -> customService.performOperation()
290
);
291
292
assertThat(exception).hasMessageThat().contains("Operation failed");
293
assertThat(exception.getErrorCode()).isEqualTo("CUSTOM_ERROR");
294
assertThat(exception.getStatusCode()).isEqualTo(500);
295
}
296
```
297
298
#### Exception Stack Trace Validation
299
300
```java
301
// Testing that exception originates from expected location
302
@Test
303
public void testException_OriginatesFromCorrectLocation() {
304
RuntimeException exception = assertThrows(
305
RuntimeException.class,
306
() -> serviceMethodThatShouldFail()
307
);
308
309
assertThat(exception).hasMessageThat().isNotNull();
310
311
// Verify stack trace contains expected method
312
StackTraceElement[] stackTrace = exception.getStackTrace();
313
assertThat(stackTrace).isNotEmpty();
314
315
boolean foundExpectedMethod = Arrays.stream(stackTrace)
316
.anyMatch(element -> element.getMethodName().equals("serviceMethodThatShouldFail"));
317
assertThat(foundExpectedMethod).isTrue();
318
}
319
```
320
321
### Exception Testing Best Practices
322
323
#### Comprehensive Exception Assertion Pattern
324
325
```java
326
/**
327
* Comprehensive exception testing helper method
328
*/
329
public static void assertExceptionDetails(
330
Throwable exception,
331
Class<? extends Throwable> expectedType,
332
String expectedMessageSubstring,
333
Class<? extends Throwable> expectedCauseType) {
334
335
assertThat(exception).isInstanceOf(expectedType);
336
assertThat(exception).hasMessageThat().contains(expectedMessageSubstring);
337
338
if (expectedCauseType != null) {
339
assertThat(exception).hasCauseThat().isInstanceOf(expectedCauseType);
340
}
341
}
342
343
// Usage
344
@Test
345
public void testComplexOperation_ProperExceptionHandling() {
346
ServiceException exception = assertThrows(
347
ServiceException.class,
348
() -> complexService.performComplexOperation()
349
);
350
351
assertExceptionDetails(
352
exception,
353
ServiceException.class,
354
"Complex operation failed",
355
DataAccessException.class
356
);
357
}
358
```
359
360
## Types
361
362
```java { .api }
363
/**
364
* Subject class for making assertions about Throwable instances.
365
*/
366
public class ThrowableSubject extends Subject {
367
/**
368
* Constructor for ThrowableSubject.
369
* @param metadata failure metadata for context
370
* @param actual the Throwable under test
371
*/
372
protected ThrowableSubject(FailureMetadata metadata, Throwable actual);
373
374
/**
375
* Returns a StringSubject for making assertions about the exception's message.
376
* The message is obtained via getMessage().
377
*/
378
public StringSubject hasMessageThat();
379
380
/**
381
* Returns a ThrowableSubject for making assertions about the exception's cause.
382
* The cause is obtained via getCause().
383
*/
384
public ThrowableSubject hasCauseThat();
385
}
386
```