How do I test that code throws or does not throw exceptions in EK9?

← Testing · Ref: Q209

EK9 provides try/catch for testing exception behavior, plus assertThrows and assertDoesNotThrow keywords for direct exception testing.

TRY/CATCH FOR EXCEPTION TESTING

Use try/catch to verify an exception is thrown:

  try
    riskyFunction()
    stdout.println("Should not reach here")
  catch
    -> ex as Exception
    stdout.println("Caught: " + $ex)

The callee must THROW explicitly. Integer division by zero does not throw - it returns an unset Integer - and a failed 'require' is a Panic that no catch block sees. So guard and throw:

  if b == 0
    throw Exception("division by zero")

ASSERTTHROWS

Verify that an expression throws an exception of a given type:

  assertThrows(Exception, riskyFunction())

The test passes only if an exception is thrown.

ASSERTDOESNOTTHROW

Verify that an expression completes without throwing:

  assertDoesNotThrow(safeFunction())

The test fails if any exception is thrown.

TESTING EXCEPTION MESSAGES

Catch and inspect exception details:

  try
    throwError()
  catch
    -> ex as Exception
    assert $ex == "expected message"

See Q134 for try/catch basics. See Q136 for throwing exceptions. See Q156 for assertions.

See Q305 for require vs assert vs throw. See Q306 for assertThrows in depth. See Q307 for assertDoesNotThrow in depth.

Example

defines module qa.testdeep.exceptions

  defines function

    riskyDivide() as pure
      ->
        a as Integer
        b as Integer
      <-
        rtn as Integer?

      require b?
      if b == 0
        throw Exception("division by zero")
      rtn: a / b

  defines program

    ExceptionTestingDemo()
      stdout <- Stdout()

      // === TRY/CATCH FOR EXCEPTION TESTING ===

      try
        result <- riskyDivide(10, 2)
        stdout.println(`10 / 2 = ${result}`)
      catch
        -> ex as Exception
        stdout.println("Unexpected error: " + $ex)

      // === TESTING ERROR CONDITIONS ===
      // riskyDivide throws explicitly: dividing by zero alone would
      // give an unset Integer, not an exception

      try
        discarded <- riskyDivide(10, 0)
        stdout.println("Should not reach here " + $discarded)
      catch
        -> ex as Exception
        stdout.println("Caught expected error: " + $ex)

      // === ASSERTTHROWS / ASSERTDOESNOTTHROW ===
      // In @Test programs:
      //   assertThrows(Exception, riskyDivide(10, 0))
      //   assertDoesNotThrow(riskyDivide(10, 2))

      stdout.println("assertThrows verifies exception is thrown")
      stdout.println("assertDoesNotThrow verifies no exception")

Common mistakes

E08091 — Parameters a and b are declared but a body that ignores them (a stub returning 0) never references them. Every parameter must be used. See ek9 -h E08091 for details.

Incorrect:

rtn: 0

Correct:

require b?
      if b == 0
        throw Exception("division by zero")
      rtn: a / b
Other ways to ask this
  • How do I verify exceptions in EK9 tests?
  • How do assertThrows and assertDoesNotThrow work in EK9?
  • How do I test error handling in EK9?

Coming from another language?

Java: JUnit assertThrows(() -> code) and assertDoesNotThrow(). Python: pytest.raises(ExceptionType). Rust: #[should_panic] attribute. Go: custom helper checking error returns. Kotlin: assertThrows<Exception> { code }. EK9: assertThrows(Type, expr) / assertDoesNotThrow(expr) keywords, plus try/catch for detailed inspection. Integer division by zero gives an unset result rather than throwing, so throw explicitly.

Keywords: error, verify, test, assertThrows, assertDoesNotThrow, coverage, catch, throw, exception