0
# Exception Handling
1
2
Comprehensive exception classes for test failures with IDE integration and detailed error reporting. MUnit provides specialized exception types that work well with development tools and provide actionable error messages.
3
4
## Capabilities
5
6
### FailException - Basic Test Failure
7
8
The primary exception type for test failures with support for custom messages, causes, and location tracking.
9
10
```scala { .api }
11
/**
12
* Primary exception for test failures
13
* @param message The failure message describing what went wrong
14
* @param cause The underlying exception that caused the failure (optional)
15
* @param isStackTracesEnabled Whether to include stack traces in output
16
* @param location Source code location where the failure occurred
17
*/
18
class FailException(
19
val message: String,
20
val cause: Throwable,
21
val isStackTracesEnabled: Boolean,
22
val location: Location
23
) extends AssertionError(message, cause) with FailExceptionLike[FailException] {
24
25
/** Create a new exception with a different message */
26
def withMessage(newMessage: String): FailException
27
}
28
```
29
30
**Usage Examples:**
31
32
```scala
33
class ExceptionExamples extends FunSuite {
34
test("explicit failure") {
35
val result = performOperation()
36
if (!result.isValid) {
37
throw new FailException(
38
s"Operation failed: ${result.error}",
39
result.cause,
40
true,
41
Location.empty
42
)
43
}
44
}
45
46
test("conditional failure with context") {
47
val data = processData()
48
if (data.isEmpty) {
49
fail("No data was processed") // This throws FailException internally
50
}
51
}
52
}
53
```
54
55
### ComparisonFailException - IDE-Compatible Comparison Failures
56
57
Specialized exception for comparison failures that integrates with IDE diff viewers and provides structured comparison data.
58
59
```scala { .api }
60
/**
61
* Exception for comparison failures with IDE integration support
62
* Extends JUnit's ComparisonFailure for IDE compatibility
63
*/
64
class ComparisonFailException(
65
val message: String,
66
val obtained: Any,
67
val expected: Any,
68
val obtainedString: String,
69
val expectedString: String,
70
val location: Location
71
) extends ComparisonFailure(message, expectedString, obtainedString) with FailExceptionLike[ComparisonFailException] {
72
73
/** Create a new exception with a different message */
74
def withMessage(newMessage: String): ComparisonFailException
75
}
76
```
77
78
**Usage Examples:**
79
80
This exception is typically thrown automatically by assertion methods like `assertEquals`, but can be used directly:
81
82
```scala
83
class ComparisonExamples extends FunSuite {
84
test("assertEquals throws ComparisonFailException") {
85
val obtained = List(1, 2, 3)
86
val expected = List(1, 2, 4)
87
88
// This will throw ComparisonFailException, which IDEs can display as a diff
89
assertEquals(obtained, expected)
90
}
91
92
test("manual comparison failure") {
93
val result = parseJson(input)
94
val expected = ExpectedResult(...)
95
96
if (result != expected) {
97
throw new ComparisonFailException(
98
"JSON parsing mismatch",
99
result,
100
expected,
101
result.toString,
102
expected.toString,
103
Location.empty
104
)
105
}
106
}
107
}
108
```
109
110
### FailSuiteException - Suite-Level Failures
111
112
Exception type for failures that should abort the entire test suite rather than just a single test.
113
114
```scala { .api }
115
/**
116
* Exception that aborts the entire test suite
117
* @param message The failure message explaining why the suite was aborted
118
* @param location Source code location where the suite failure occurred
119
*/
120
class FailSuiteException(
121
override val message: String,
122
override val location: Location
123
) extends FailException(message, location) {
124
125
/** Create a new exception with a different message */
126
def withMessage(newMessage: String): FailSuiteException
127
}
128
```
129
130
**Usage Examples:**
131
132
```scala
133
class SuiteFailureExamples extends FunSuite {
134
override def beforeAll(): Unit = {
135
val dbConnection = connectToDatabase()
136
if (!dbConnection.isValid) {
137
failSuite("Cannot connect to test database")
138
// This throws FailSuiteException and stops all tests
139
}
140
}
141
142
test("this test won't run if suite failed") {
143
// This test is skipped if beforeAll() threw FailSuiteException
144
assert(true)
145
}
146
}
147
```
148
149
### FailExceptionLike - Common Exception Behavior
150
151
Trait that provides common functionality for all MUnit failure exceptions.
152
153
```scala { .api }
154
/**
155
* Common behavior for all MUnit failure exceptions
156
* @tparam T The concrete exception type
157
*/
158
trait FailExceptionLike[T <: AssertionError] extends Serializable {
159
160
/** Create a new exception with a different message */
161
def withMessage(message: String): T
162
163
/** Transform the message using a function */
164
def updateMessage(f: String => String): T
165
166
/** Source code location where the failure occurred */
167
def location: Location
168
169
/** Whether stack traces are enabled for this exception */
170
def isStackTracesEnabled: Boolean
171
}
172
```
173
174
### Exception Utilities
175
176
Utility functions for working with exceptions and error handling.
177
178
```scala { .api }
179
/**
180
* Utilities for exception handling
181
*/
182
object Exceptions {
183
/**
184
* Find the root cause of a nested exception
185
* @param x The exception to unwrap
186
* @return The deepest cause in the exception chain
187
*/
188
def rootCause(x: Throwable): Throwable
189
}
190
```
191
192
**Usage Examples:**
193
194
```scala
195
class ExceptionUtilityExamples extends FunSuite {
196
test("finding root cause") {
197
val rootCause = new IllegalArgumentException("Invalid input")
198
val wrapped = new RuntimeException("Processing failed", rootCause)
199
val nested = new Exception("Operation failed", wrapped)
200
201
val actual = Exceptions.rootCause(nested)
202
assertEquals(actual, rootCause)
203
assertEquals(actual.getMessage, "Invalid input")
204
}
205
}
206
```
207
208
### ComparisonFailExceptionHandler - Custom Exception Handling
209
210
Interface for customizing how comparison failures are handled and reported.
211
212
```scala { .api }
213
/**
214
* Handler for customizing comparison failure behavior
215
*/
216
trait ComparisonFailExceptionHandler {
217
/**
218
* Handle a comparison failure with custom logic
219
* @param message The failure message
220
* @param obtained String representation of the obtained value
221
* @param expected String representation of the expected value
222
* @param location Source code location of the failure
223
* @return Never returns (always throws an exception)
224
*/
225
def handle(
226
message: String,
227
obtained: String,
228
expected: String,
229
location: Location
230
): Nothing
231
}
232
```
233
234
### Special Exception Types
235
236
Additional exception types for specific testing scenarios.
237
238
```scala { .api }
239
/**
240
* Exception for flaky test failures (internal use)
241
* Extends FailException but with NoStackTrace for cleaner output
242
*/
243
class FlakyFailure(error: Throwable) extends FailException with NoStackTrace with Serializable
244
```
245
246
## Error Handling Patterns
247
248
### Graceful Failure Handling
249
250
```scala
251
class ErrorHandlingExamples extends FunSuite {
252
test("graceful failure with context") {
253
val input = getUserInput()
254
255
try {
256
val result = processInput(input)
257
assertEquals(result.status, "success")
258
} catch {
259
case e: ProcessingException =>
260
fail(
261
s"Processing failed for input: $input",
262
e
263
)
264
}
265
}
266
267
test("assertion with debugging context") {
268
val users = getUsers()
269
val activeUsers = users.filter(_.isActive)
270
271
assert(
272
activeUsers.nonEmpty,
273
s"Expected active users but got: ${users.map(u => s"${u.name}(${u.status})").mkString(", ")}"
274
)
275
}
276
}
277
```
278
279
### Suite-Level Error Handling
280
281
```scala
282
class DatabaseSuite extends FunSuite {
283
private var database: Database = _
284
285
override def beforeAll(): Unit = {
286
try {
287
database = Database.connect(testConfig)
288
database.migrate()
289
} catch {
290
case e: SQLException =>
291
failSuite(s"Failed to setup test database: ${e.getMessage}")
292
}
293
}
294
295
override def afterAll(): Unit = {
296
try {
297
database.close()
298
} catch {
299
case e: SQLException =>
300
// Log but don't fail suite during cleanup
301
println(s"Warning: Failed to close database: ${e.getMessage}")
302
}
303
}
304
}
305
```
306
307
### Custom Assertion Methods
308
309
```scala
310
object CustomAssertions {
311
def assertValidEmail(email: String)(implicit loc: Location): Unit = {
312
if (!email.contains("@")) {
313
throw new FailException(
314
s"Invalid email format: '$email' (missing @)",
315
null,
316
true,
317
loc
318
)
319
}
320
}
321
322
def assertBetween[T: Ordering](value: T, min: T, max: T)(implicit loc: Location): Unit = {
323
val ord = implicitly[Ordering[T]]
324
if (ord.lt(value, min) || ord.gt(value, max)) {
325
throw new FailException(
326
s"Value $value is not between $min and $max",
327
null,
328
true,
329
loc
330
)
331
}
332
}
333
}
334
335
class CustomAssertionExamples extends FunSuite {
336
import CustomAssertions._
337
338
test("email validation") {
339
assertValidEmail("user@example.com")
340
}
341
342
test("range validation") {
343
val score = calculateScore()
344
assertBetween(score, 0.0, 100.0)
345
}
346
}