or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdconfiguration.mdexceptions.mdfixtures.mdindex.mdtest-suites.mdtransforms.md
tile.json

exceptions.mddocs/

Exception Handling

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.

Capabilities

FailException - Basic Test Failure

The primary exception type for test failures with support for custom messages, causes, and location tracking.

/**
 * Primary exception for test failures
 * @param message The failure message describing what went wrong
 * @param cause The underlying exception that caused the failure (optional)
 * @param isStackTracesEnabled Whether to include stack traces in output
 * @param location Source code location where the failure occurred
 */
class FailException(
  val message: String,
  val cause: Throwable,
  val isStackTracesEnabled: Boolean,
  val location: Location
) extends AssertionError(message, cause) with FailExceptionLike[FailException] {
  
  /** Create a new exception with a different message */
  def withMessage(newMessage: String): FailException
}

Usage Examples:

class ExceptionExamples extends FunSuite {
  test("explicit failure") {
    val result = performOperation()
    if (!result.isValid) {
      throw new FailException(
        s"Operation failed: ${result.error}",
        result.cause,
        true,
        Location.empty
      )
    }
  }
  
  test("conditional failure with context") {
    val data = processData()
    if (data.isEmpty) {
      fail("No data was processed") // This throws FailException internally
    }
  }
}

ComparisonFailException - IDE-Compatible Comparison Failures

Specialized exception for comparison failures that integrates with IDE diff viewers and provides structured comparison data.

/**
 * Exception for comparison failures with IDE integration support
 * Extends JUnit's ComparisonFailure for IDE compatibility
 */
class ComparisonFailException(
  val message: String,
  val obtained: Any,
  val expected: Any,
  val obtainedString: String,
  val expectedString: String,
  val location: Location
) extends ComparisonFailure(message, expectedString, obtainedString) with FailExceptionLike[ComparisonFailException] {
  
  /** Create a new exception with a different message */
  def withMessage(newMessage: String): ComparisonFailException
}

Usage Examples:

This exception is typically thrown automatically by assertion methods like assertEquals, but can be used directly:

class ComparisonExamples extends FunSuite {
  test("assertEquals throws ComparisonFailException") {
    val obtained = List(1, 2, 3)
    val expected = List(1, 2, 4)
    
    // This will throw ComparisonFailException, which IDEs can display as a diff
    assertEquals(obtained, expected)
  }
  
  test("manual comparison failure") {
    val result = parseJson(input)
    val expected = ExpectedResult(...)
    
    if (result != expected) {
      throw new ComparisonFailException(
        "JSON parsing mismatch",
        result,
        expected,
        result.toString,
        expected.toString,
        Location.empty
      )
    }
  }
}

FailSuiteException - Suite-Level Failures

Exception type for failures that should abort the entire test suite rather than just a single test.

/**
 * Exception that aborts the entire test suite
 * @param message The failure message explaining why the suite was aborted
 * @param location Source code location where the suite failure occurred
 */
class FailSuiteException(
  override val message: String,
  override val location: Location
) extends FailException(message, location) {
  
  /** Create a new exception with a different message */
  def withMessage(newMessage: String): FailSuiteException
}

Usage Examples:

class SuiteFailureExamples extends FunSuite {
  override def beforeAll(): Unit = {
    val dbConnection = connectToDatabase()
    if (!dbConnection.isValid) {
      failSuite("Cannot connect to test database")
      // This throws FailSuiteException and stops all tests
    }
  }
  
  test("this test won't run if suite failed") {
    // This test is skipped if beforeAll() threw FailSuiteException
    assert(true)
  }
}

FailExceptionLike - Common Exception Behavior

Trait that provides common functionality for all MUnit failure exceptions.

/**
 * Common behavior for all MUnit failure exceptions
 * @tparam T The concrete exception type
 */
trait FailExceptionLike[T <: AssertionError] extends Serializable {
  
  /** Create a new exception with a different message */
  def withMessage(message: String): T
  
  /** Transform the message using a function */
  def updateMessage(f: String => String): T
  
  /** Source code location where the failure occurred */
  def location: Location
  
  /** Whether stack traces are enabled for this exception */
  def isStackTracesEnabled: Boolean
}

Exception Utilities

Utility functions for working with exceptions and error handling.

/**
 * Utilities for exception handling
 */
object Exceptions {
  /** 
   * Find the root cause of a nested exception
   * @param x The exception to unwrap
   * @return The deepest cause in the exception chain
   */
  def rootCause(x: Throwable): Throwable
}

Usage Examples:

class ExceptionUtilityExamples extends FunSuite {
  test("finding root cause") {
    val rootCause = new IllegalArgumentException("Invalid input")
    val wrapped = new RuntimeException("Processing failed", rootCause)
    val nested = new Exception("Operation failed", wrapped)
    
    val actual = Exceptions.rootCause(nested)
    assertEquals(actual, rootCause)
    assertEquals(actual.getMessage, "Invalid input")
  }
}

ComparisonFailExceptionHandler - Custom Exception Handling

Interface for customizing how comparison failures are handled and reported.

/**
 * Handler for customizing comparison failure behavior
 */
trait ComparisonFailExceptionHandler {
  /**
   * Handle a comparison failure with custom logic
   * @param message The failure message
   * @param obtained String representation of the obtained value
   * @param expected String representation of the expected value  
   * @param location Source code location of the failure
   * @return Never returns (always throws an exception)
   */
  def handle(
    message: String,
    obtained: String,
    expected: String,
    location: Location
  ): Nothing
}

Special Exception Types

Additional exception types for specific testing scenarios.

/**
 * Exception for flaky test failures (internal use)  
 * Extends FailException but with NoStackTrace for cleaner output
 */
class FlakyFailure(error: Throwable) extends FailException with NoStackTrace with Serializable

Error Handling Patterns

Graceful Failure Handling

class ErrorHandlingExamples extends FunSuite {
  test("graceful failure with context") {
    val input = getUserInput()
    
    try {
      val result = processInput(input)
      assertEquals(result.status, "success")
    } catch {
      case e: ProcessingException =>
        fail(
          s"Processing failed for input: $input",
          e
        )
    }
  }
  
  test("assertion with debugging context") {
    val users = getUsers()
    val activeUsers = users.filter(_.isActive)
    
    assert(
      activeUsers.nonEmpty,
      s"Expected active users but got: ${users.map(u => s"${u.name}(${u.status})").mkString(", ")}"
    )
  }
}

Suite-Level Error Handling

class DatabaseSuite extends FunSuite {
  private var database: Database = _
  
  override def beforeAll(): Unit = {
    try {
      database = Database.connect(testConfig)
      database.migrate()
    } catch {
      case e: SQLException =>
        failSuite(s"Failed to setup test database: ${e.getMessage}")
    }
  }
  
  override def afterAll(): Unit = {
    try {
      database.close()
    } catch {
      case e: SQLException =>
        // Log but don't fail suite during cleanup
        println(s"Warning: Failed to close database: ${e.getMessage}")
    }
  }
}

Custom Assertion Methods

object CustomAssertions {
  def assertValidEmail(email: String)(implicit loc: Location): Unit = {
    if (!email.contains("@")) {
      throw new FailException(
        s"Invalid email format: '$email' (missing @)",
        null,
        true,
        loc
      )
    }
  }
  
  def assertBetween[T: Ordering](value: T, min: T, max: T)(implicit loc: Location): Unit = {
    val ord = implicitly[Ordering[T]]
    if (ord.lt(value, min) || ord.gt(value, max)) {
      throw new FailException(
        s"Value $value is not between $min and $max",
        null,
        true,
        loc
      )
    }
  }
}

class CustomAssertionExamples extends FunSuite {
  import CustomAssertions._
  
  test("email validation") {
    assertValidEmail("user@example.com")
  }
  
  test("range validation") {
    val score = calculateScore()
    assertBetween(score, 0.0, 100.0)
  }
}