or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-assertions.mdcore-assertions.mdexception-handling.mdhamcrest-matchers.mdindex.mdtest-assumptions.mdtest-lifecycle.mdtest-runners.md

exception-handling.mddocs/

0

# Exception Handling

1

2

The Scala.js JUnit runtime provides enhanced exception classes for detailed test failure reporting, including string comparison diffs and assumption violation handling. These exceptions integrate with the test framework to provide clear, actionable error messages.

3

4

## Core Exception Classes

5

6

### ComparisonFailure

7

8

```scala { .api }

9

class ComparisonFailure(message: String, expected: String, actual: String) extends AssertionError {

10

def getMessage(): String

11

def getExpected(): String

12

def getActual(): String

13

}

14

15

object ComparisonFailure {

16

// Factory methods and comparison utilities

17

}

18

```

19

20

ComparisonFailure provides enhanced error reporting for string comparisons with automatic diff generation.

21

22

**Basic Usage:**

23

```scala

24

// Thrown automatically by assertEquals for strings

25

assertEquals("Hello World", "Hello Universe")

26

// Throws: ComparisonFailure with diff highlighting the difference

27

```

28

29

**Manual Usage:**

30

```scala

31

def compareComplexStrings(expected: String, actual: String): Unit = {

32

if (expected != actual) {

33

throw new ComparisonFailure("String comparison failed", expected, actual)

34

}

35

}

36

```

37

38

**Error Message Format:**

39

```

40

expected:<Hello [World]> but was:<Hello [Universe]>

41

```

42

43

The brackets `[]` highlight the differing portions of the strings.

44

45

### AssumptionViolatedException

46

47

```scala { .api }

48

class AssumptionViolatedException extends RuntimeException {

49

// Multiple constructor overloads:

50

def this(message: String) = this()

51

def this(assumption: String, t: Throwable) = this()

52

def this(assumption: String, actual: Any, matcher: Matcher[_]) = this()

53

}

54

```

55

56

**Usage:**

57

```scala

58

import org.junit.Assume._

59

60

@Test

61

def shouldRunOnLinux(): Unit = {

62

assumeTrue("Test requires Linux", System.getProperty("os.name").contains("Linux"))

63

// If not on Linux, throws AssumptionViolatedException -> test is skipped

64

}

65

```

66

67

### TestCouldNotBeSkippedException

68

69

```scala { .api }

70

class TestCouldNotBeSkippedException(cause: internal.AssumptionViolatedException) extends RuntimeException {

71

// Exception when test cannot be skipped due to assumption failure

72

}

73

```

74

75

This exception wraps internal assumption violations when tests cannot be properly skipped.

76

77

## Internal Exception Handling

78

79

### Internal AssumptionViolatedException

80

81

```scala { .api }

82

// Internal package version

83

class internal.AssumptionViolatedException extends RuntimeException with SelfDescribing {

84

def getMessage(): String

85

def describeTo(description: Description): Unit

86

87

// Enhanced with Hamcrest integration for detailed mismatch descriptions

88

}

89

```

90

91

**Constructor Variants:**

92

```scala

93

// Internal version constructors

94

new internal.AssumptionViolatedException(message: String)

95

new internal.AssumptionViolatedException(message: String, cause: Throwable)

96

new internal.AssumptionViolatedException(assumption: String, value: Any, matcher: Matcher[_])

97

```

98

99

### ArrayComparisonFailure

100

101

```scala { .api }

102

class ArrayComparisonFailure(message: String, cause: AssertionError, index: Int) extends AssertionError {

103

def addDimension(index: Int): Unit

104

def getMessage(): String

105

override def toString(): String

106

}

107

108

object ArrayComparisonFailure {

109

// Factory methods for creating array-specific failures

110

}

111

```

112

113

Provides detailed error reporting for array comparison failures with index information.

114

115

**Example Error Messages:**

116

```scala

117

// Single dimension array

118

val expected = Array(1, 2, 3)

119

val actual = Array(1, 5, 3)

120

assertArrayEquals(expected, actual)

121

// Error: "arrays first differed at element [1]; expected:<2> but was:<5>"

122

123

// Multi-dimensional array

124

val expected = Array(Array(1, 2), Array(3, 4))

125

val actual = Array(Array(1, 2), Array(3, 9))

126

assertArrayEquals(expected, actual)

127

// Error: "arrays first differed at element [1][1]; expected:<4> but was:<9>"

128

```

129

130

## Exception Flow and Handling

131

132

### Test Execution Exception Flow

133

134

```scala

135

class TestExecutor {

136

def executeTest(testMethod: Method, testInstance: Any): TestResult = {

137

try {

138

// Execute @Before methods

139

executeBeforeMethods(testInstance)

140

141

// Execute test method

142

testMethod.invoke(testInstance)

143

144

TestResult.Success

145

146

} catch {

147

// Test failures - assertion errors

148

case e: AssertionError =>

149

TestResult.Failure(e)

150

151

// Test errors - unexpected exceptions

152

case e: Exception =>

153

TestResult.Error(e)

154

155

// Assumption violations - skip test

156

case e: AssumptionViolatedException =>

157

TestResult.Skipped(e)

158

159

} finally {

160

// Always execute @After methods

161

try {

162

executeAfterMethods(testInstance)

163

} catch {

164

case e: Exception =>

165

// Log but don't fail test if @After fails

166

logAfterMethodFailure(e)

167

}

168

}

169

}

170

}

171

```

172

173

### Exception Chaining and Root Cause Analysis

174

175

```scala

176

class EnhancedExceptionReporter {

177

def analyzeException(e: Throwable): ExceptionReport = {

178

e match {

179

case cf: ComparisonFailure =>

180

ExceptionReport(

181

type = "String Comparison Failure",

182

message = cf.getMessage(),

183

expected = Some(cf.getExpected()),

184

actual = Some(cf.getActual()),

185

diff = generateDiff(cf.getExpected(), cf.getActual())

186

)

187

188

case acf: ArrayComparisonFailure =>

189

ExceptionReport(

190

type = "Array Comparison Failure",

191

message = acf.getMessage(),

192

index = Some(extractFailureIndex(acf)),

193

cause = Option(acf.getCause()).map(analyzeException)

194

)

195

196

case ave: AssumptionViolatedException =>

197

ExceptionReport(

198

type = "Assumption Violation",

199

message = ave.getMessage(),

200

skipped = true

201

)

202

203

case ae: AssertionError =>

204

ExceptionReport(

205

type = "Assertion Failure",

206

message = ae.getMessage(),

207

stackTrace = ae.getStackTrace()

208

)

209

}

210

}

211

}

212

```

213

214

## Custom Exception Creation

215

216

### Creating Custom ComparisonFailure

217

218

```scala

219

object CustomAsserts {

220

def assertJsonEquals(expected: String, actual: String): Unit = {

221

val expectedJson = parseJson(expected)

222

val actualJson = parseJson(actual)

223

224

if (expectedJson != actualJson) {

225

val prettyExpected = formatJson(expectedJson)

226

val prettyActual = formatJson(actualJson)

227

throw new ComparisonFailure("JSON comparison failed", prettyExpected, prettyActual)

228

}

229

}

230

231

def assertXmlEquals(expected: String, actual: String): Unit = {

232

val expectedXml = normalizeXml(expected)

233

val actualXml = normalizeXml(actual)

234

235

if (expectedXml != actualXml) {

236

throw new ComparisonFailure("XML comparison failed", expectedXml, actualXml)

237

}

238

}

239

}

240

```

241

242

### Custom Assumption Exceptions

243

244

```scala

245

object CustomAssumptions {

246

def assumeNetworkAvailable(): Unit = {

247

try {

248

val socket = new Socket("www.google.com", 80)

249

socket.close()

250

} catch {

251

case _: IOException =>

252

throw new AssumptionViolatedException("Network connectivity required for this test")

253

}

254

}

255

256

def assumeMinimumJavaVersion(major: Int, minor: Int): Unit = {

257

val version = System.getProperty("java.version")

258

val parts = version.split("\\.")

259

val actualMajor = parts(0).toInt

260

val actualMinor = if (parts.length > 1) parts(1).toInt else 0

261

262

if (actualMajor < major || (actualMajor == major && actualMinor < minor)) {

263

throw new AssumptionViolatedException(

264

s"Java $major.$minor+ required, but running on $version"

265

)

266

}

267

}

268

}

269

```

270

271

## Stack Trace Enhancement

272

273

### Scala.js Stack Trace Filtering

274

275

The Reporter class provides enhanced stack trace filtering for cleaner error output:

276

277

```scala

278

class Reporter {

279

private def logTrace(t: Throwable): Unit = {

280

val trace = t.getStackTrace.dropWhile { elem =>

281

elem.getFileName() != null && (

282

elem.getFileName().contains("StackTrace.scala") ||

283

elem.getFileName().contains("Throwables.scala")

284

)

285

}

286

287

val relevantTrace = trace.takeWhile { elem =>

288

!elem.toString.startsWith("org.junit.") &&

289

!elem.toString.startsWith("org.hamcrest.")

290

}

291

292

relevantTrace.foreach { elem =>

293

log(_.error, " at " + formatStackTraceElement(elem))

294

}

295

}

296

297

private def formatStackTraceElement(elem: StackTraceElement): String = {

298

val className = settings.decodeName(elem.getClassName())

299

val methodName = settings.decodeName(elem.getMethodName())

300

val location = if (elem.getFileName() != null && elem.getLineNumber() >= 0) {

301

s"${elem.getFileName()}:${elem.getLineNumber()}"

302

} else {

303

"Unknown Source"

304

}

305

306

s"$className.$methodName($location)"

307

}

308

}

309

```

310

311

### Exception Message Formatting

312

313

```scala

314

object ExceptionFormatter {

315

def formatAssertionError(message: String, expected: Any, actual: Any): String = {

316

val prefix = if (message != null && message.nonEmpty) s"$message " else ""

317

val expectedStr = String.valueOf(expected)

318

val actualStr = String.valueOf(actual)

319

320

if (expectedStr == actualStr) {

321

// Same string representation, show types

322

val expectedType = if (expected != null) expected.getClass.getName else "null"

323

val actualType = if (actual != null) actual.getClass.getName else "null"

324

s"${prefix}expected: $expectedType<$expectedStr> but was: $actualType<$actualStr>"

325

} else {

326

s"${prefix}expected:<$expectedStr> but was:<$actualStr>"

327

}

328

}

329

}

330

```

331

332

## Integration with IDE and Build Tools

333

334

### IDE Integration

335

336

Exception messages are formatted for optimal display in IDEs:

337

338

- **ComparisonFailure**: IDEs can show side-by-side diff views

339

- **ArrayComparisonFailure**: Click-to-navigate to specific array indices

340

- **AssumptionViolatedException**: Marked as "skipped" rather than "failed"

341

342

### Build Tool Integration

343

344

```scala

345

// SBT test output

346

[info] MyTest:

347

[info] - shouldCompareStrings *** FAILED ***

348

[info] expected:<Hello [World]> but was:<Hello [Universe]> (MyTest.scala:15)

349

[info] - shouldRunOnLinux *** SKIPPED ***

350

[info] Test requires Linux (MyTest.scala:20)

351

```

352

353

### CI/CD Integration

354

355

Exception handling integrates with continuous integration:

356

357

- **Failed tests**: Return non-zero exit codes

358

- **Skipped tests**: Don't fail builds but are reported

359

- **Error details**: Captured in test reports (JUnit XML, etc.)

360

361

## Best Practices

362

363

1. **Use Appropriate Exception Types**: Let JUnit choose the right exception type automatically:

364

```scala

365

// Good - uses ComparisonFailure automatically

366

assertEquals(expected, actual)

367

368

// Avoid - manual exception throwing unless needed

369

if (expected != actual) throw new AssertionError("Values differ")

370

```

371

372

2. **Provide Meaningful Messages**: Include context in assertion messages:

373

```scala

374

assertEquals("User name should match", expectedName, user.getName())

375

assertArrayEquals("Pixel values should be identical", expectedPixels, actualPixels)

376

```

377

378

3. **Handle Assumptions Properly**: Use assumptions for environmental dependencies:

379

```scala

380

assumeTrue("Database connection required", databaseAvailable())

381

// Better than: if (!databaseAvailable()) return; // silently skip

382

```

383

384

4. **Chain Exceptions Appropriately**: Preserve original exception context:

385

```scala

386

try {

387

complexOperation()

388

} catch {

389

case e: SomeSpecificException =>

390

throw new AssertionError("Complex operation failed", e)

391

}

392

```