How do I test that code throws an exception in EK9?
← Error Handling and Exceptions · Ref: Q306
EK9 provides assertThrows as a built-in keyword for verifying that an expression throws an expected exception type. It is only valid inside @Test programs.
BASIC SYNTAX
assertThrows(ExceptionType, expression)
The test passes if the expression throws an exception of the specified type. The test fails if no exception is thrown or a different type is thrown.
AS A STATEMENT
Use assertThrows when you only need to verify the exception occurs:
assertThrows(Exception, riskyFunction())
If riskyFunction() does not throw, the test fails with a structured error message showing the location, expression, expected type, and what actually happened.
AS AN EXPRESSION (Capture)
Capture the thrown exception for further inspection:
caught <- assertThrows(Exception, riskyFunction()) assert caught.reason()? assert $caught == "Expected error message"
The returned value is the caught exception, allowing inspection of its reason, exit code, and custom fields.
FAILURE OUTPUT
When assertThrows fails, EK9 provides structured diagnostic output:
assertThrows FAILED Location: ./dev/tests.ek9:5:3 Expression: riskyFunction() Expected: org.ek9.lang::Exception Actual: No exception was thrown
This structured output is captured at compile time from the AST, not generated at runtime.
CUSTOM EXCEPTION TYPES
assertThrows works with custom exception types:
assertThrows(ValidationError, validate(badInput))
The exception type must match exactly. A parent type will not catch a child type in assertThrows.
COMPILE-TIME RESTRICTION
assertThrows is only valid in @Test programs. Using it in production code causes a compile error. This prevents test assertions from accidentally appearing in production.
See Q134 for try/catch. See Q305 for require vs assert vs throw. See Q307 for assertDoesNotThrow. See Q209 for exception testing patterns.
Example
defines module qa.errorhandling.assertthrows defines class <?- Custom exception carrying a field name for validation errors. -?> ValidationError extends Exception field <- String() ValidationError() -> reason as String fieldName as String super(reason) this.field :=: fieldName field() as pure <- rtn as String: field default operator ? defines function <?- Always throws an exception for testing purposes. -?> failingOperation() throw Exception("Operation failed") <?- Validates and throws ValidationError for bad input. -?> validatePositive() -> number as Integer <- rtn as Integer: number if number < 0 ex <- ValidationError("Must be positive", "number") throw ex <?- Safe operation that does not throw. -?> safeAdd() as pure -> a as Integer b as Integer <- rtn as Integer: a + b defines program @Test AssertThrowsStatementTest() stdout <- Stdout() stdout.println("=== assertThrows as statement ===") assertThrows(Exception, failingOperation()) stdout.println("Verified: failingOperation() throws Exception") assertThrows(ValidationError, validatePositive(-5)) stdout.println("Verified: validatePositive(-5) throws ValidationError") @Test AssertThrowsCaptureTest() stdout <- Stdout() stdout.println("=== assertThrows as expression (capture) ===") caught <- assertThrows(Exception, failingOperation()) assert caught? stdout.println("Caught reason: " + caught.reason()) valError <- assertThrows(ValidationError, validatePositive(-10)) assert valError? stdout.println("Validation field: " + valError.field()) stdout.println("Validation reason: " + valError.reason())
Common mistakes
E04030 — The first argument to assertThrows must be an Exception type or subclass. Using a non-exception type like String triggers E04030 — type must be of Exception type. Use Exception or a custom exception class that extends Exception. See ek9 -h E04030 for details.
Incorrect:
assertThrows(String, failingOperation())
Correct:
assertThrows(Exception, failingOperation())
Other ways to ask this
- How does assertThrows work in EK9?
- How do I verify an exception is thrown in a test?
- What is the EK9 equivalent of JUnit assertThrows?
Coming from another language?
Java: JUnit assertThrows(() -> code, ExceptionType.class) returns the exception. Python: pytest.raises(ExceptionType) as context manager. Rust: #[should_panic] attribute on test function (coarse). Go: manual check with recover() in test helper. Kotlin: assertThrows<ExceptionType> { code }. C#: Assert.Throws<ExceptionType>(() => code). EK9: assertThrows(ExceptionType, expression) is a built-in keyword, not a library function. Compile-time restricted to @Test programs. Structured failure output with AST-captured source location.
Keywords: structured, verify, handle, exception, test, catch, testing, assertThrows, diagnostic, capture