Question

Test Expected Exceptions in Kotlin with JUnit

kotlinunit-testingtestingexceptionjunit4

Question

How can I declare that a Kotlin test is expected to throw an ArithmeticException, equivalent to this JUnit 4 Java test?

@Test(expected = ArithmeticException.class)
public void omg() {
    int blackHole = 1 / 0;
}

I tried ArithmeticException and ArithmeticException.class in Kotlin, but neither syntax compiles.

Short Answer

You will learn how Kotlin refers to classes in annotations, how to test exceptions with JUnit 4, and the preferred assertion-based approach used by JUnit 5.

Concept

Kotlin and Java use different syntax when referring to a class.

In Java, ArithmeticException.class produces a Class<ArithmeticException> object. In Kotlin, the equivalent class reference is:

ArithmeticException::class

For a JUnit 4 test, the expected annotation argument expects a Kotlin class reference (KClass). Therefore, use ArithmeticException::class.

import org.junit.Test

class CalculatorTest {
    @Test(expected = ArithmeticException::class)
    fun omg() {
        val denominator = 0
        val blackHole = 1 / denominator
    }
}

Testing exceptions matters because errors are part of an application's behavior. For example, code may reject invalid input, deny unauthorized access, or fail when required data is missing. A test should confirm that the correct failure occurs in those situations.

Mental Model

Think of a class reference as writing down the address of a building rather than creating a person who lives there.

  • ArithmeticException() creates an exception object: it is like creating a person.
  • ArithmeticException::class refers to the exception type itself: it is like writing the building's address.

JUnit's expected setting needs the type of exception that should occur, so it needs the address: ArithmeticException::class.

Take Quiz

Syntax and Examples

With JUnit 4, declare the expected exception in the @Test annotation:

import org.junit.Test

class DivisionTest {
    @Test(expected = ArithmeticException::class)
    fun divisionByZeroThrows() {
        val divisor = 0
        val result = 10 / divisor
    }
}

ArithmeticException::class means “the ArithmeticException class.”

Avoid using a constant expression such as 10 / 0 in an example. Kotlin may report division by zero during compilation instead of allowing the test to run. Putting zero in a variable makes the division occur at runtime.

JUnit 5 syntax

JUnit 5 commonly uses assertThrows. This makes it clear which exact statement is expected to fail.

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class DivisionTest {
    @Test
    fun divisionByZeroThrows() {
        val exception = assertThrows<ArithmeticException> {
            val divisor = 0
            10 / divisor
        }

        println(exception.message)
    }
}

The generic type in assertThrows<ArithmeticException> specifies the expected exception type.

Step by Step Execution

Consider this JUnit 4 test:

@Test(expected = ArithmeticException::class)
fun divisionByZeroThrows() {
    val divisor = 0
    val result = 10 / divisor
}

Execution proceeds as follows:

  1. JUnit discovers the function because it has the @Test annotation.
  2. JUnit reads expected = ArithmeticException::class and records that this test should throw an ArithmeticException.
  3. The test function starts and stores 0 in divisor.
  4. Kotlin evaluates 10 / divisor on the JVM.
  5. Integer division by zero throws ArithmeticException.
  6. JUnit sees the expected exception and marks the test as passed.

If the division did not throw an exception, JUnit would fail the test because an exception was expected.

Real World Use Cases

Exception tests are useful whenever failure is an intentional part of the contract.

  • Input validation: reject a negative quantity or malformed value.
  • Authentication: throw an access-related exception when a user is not authorized.
  • Data lookup: signal that a requested record does not exist.
  • File processing: verify behavior when a required file cannot be read.
  • Parsing: ensure invalid JSON, dates, or identifiers produce a meaningful failure.

For example, a money transfer service might reject a non-positive amount:

fun transfer(amount: Int) {
    require(amount > 0) { "Amount must be positive" }
}

A test can verify that invalid input throws IllegalArgumentException.

Take Quiz

Real Codebase Usage

In production codebases, developers usually test both the exception type and useful details such as its message. JUnit 5's assertThrows is often preferred because the risky code is wrapped in a small block.

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals

class TransferServiceTest {
    @Test
    fun rejectsNonPositiveAmount() {
        val exception = assertThrows<IllegalArgumentException> {
            transfer(0)
        }

        assertEquals("Amount must be positive", exception.message)
    }
}

This style prevents a test from passing because of an exception thrown earlier than intended. It also supports a common validation pattern: fail early with require, check, or a domain-specific exception.

Use a domain-specific exception when callers need to distinguish one business failure from another:

class InsufficientFundsException(message: String) : RuntimeException(message)

Common Mistakes

Using Java's .class syntax

This is Java syntax and is invalid in Kotlin:

// Does not compile
@Test(expected = ArithmeticException.class)
fun test() {}

Use ::class instead:

@Test(expected = ArithmeticException::class)
fun test() {}

Forgetting the ::class reference

This looks like a class is being used as a value, which Kotlin does not allow:

// Does not compile
@Test(expected = ArithmeticException)
fun test() {}

Use ArithmeticException::class.

Creating an exception instead of referring to its type

// Not appropriate for JUnit 4's expected property
@Test(expected = ArithmeticException())
fun test() {}

ArithmeticException() creates an object. The annotation requires a class reference.

Letting the wrong line throw

With JUnit 4's expected, any matching exception anywhere in the test makes it pass:

@Test(expected = IllegalArgumentException::class)
fun test() {
    require(false) // The test already passes here.
    codeThatWasSupposedToThrow()
}

Prefer JUnit 5 assertThrows when you need to identify the exact operation that must fail.

Mixing JUnit 4 and JUnit 5 imports

JUnit 4 uses:

import org.junit.Test

JUnit 5 uses:

import org.junit.jupiter.api.Test

Ensure your test dependency and imports use the same JUnit version.

Comparisons

ApproachBest useCan inspect exception?Important behavior
JUnit 4 @Test(expected = ...)Simple legacy JUnit 4 testsNoAny matching exception in the whole test passes it
JUnit 5 assertThrows<T> { ... }New JUnit 5 testsYesOnly the code inside the block is checked
try / catchRare custom assertionsYesMore verbose and easier to write incorrectly

Class reference versus object creation

Kotlin expressionMeaning
ArithmeticException::classA reference to the exception class
ArithmeticException()A newly created exception object
ArithmeticException::class.javaThe underlying Java Class object, needed by some Java APIs

For JUnit 4 Kotlin annotations, use ArithmeticException::class.

Take Quiz

Cheat Sheet

// JUnit 4: expected exception in an annotation
import org.junit.Test

@Test(expected = IllegalArgumentException::class)
fun rejectsInvalidInput() {
    require(false)
}
// JUnit 5: preferred focused assertion
import org.junit.jupiter.api.assertThrows

val exception = assertThrows<IllegalArgumentException> {
    require(false) { "Invalid input" }
}
  • Kotlin class reference: TypeName::class
  • Java class reference: TypeName.class
  • Create an object: TypeName()
  • Use assertThrows when you need to inspect the message or isolate the failing statement.
  • JUnit 4 expected accepts the declared exception type and its subclasses.

FAQ

What is the Kotlin equivalent of Exception.class?

Use Exception::class.

How do I expect an exception in a Kotlin JUnit 4 test?

Use @Test(expected = MyException::class) with import org.junit.Test.

Why does ArithmeticException.class not compile in Kotlin?

.class is Java syntax. Kotlin uses ::class for a class reference.

Why does Kotlin say that ArithmeticException has no companion object?

Without ::class or (), Kotlin interprets the class name as though you are trying to access a value on the class. A class itself is not a value in that position.

Should I use JUnit 4 expected or JUnit 5 assertThrows?

For new JUnit 5 tests, prefer assertThrows. It scopes the expected failure to a specific block and returns the exception for further assertions.

Can I test an exception message too?

Yes. Use JUnit 5 assertThrows to capture the exception, then assert its message.

Does a JUnit 4 expected exception test pass for subclasses?

Yes. A test expecting a base exception type also passes when code throws a subtype of that exception.

Take Quiz

Mini Project

Description

Create a small Kotlin validator for account registration data. The validator should reject invalid values with meaningful exceptions, and the tests should verify both the exception type and message. This mirrors validation used in API endpoints and service-layer code.

Goal

Write exception tests for invalid registration input using JUnit 5 assertThrows.

Requirements

  • Create a register function that accepts an email and an age.
  • Reject a blank email with IllegalArgumentException.
  • Reject an age below 18 with IllegalArgumentException.
  • Return a success message for valid input.
  • Test both invalid cases and assert their exception messages.
  • Test one valid registration case.
Take Quiz

Keep learning

Related questions

View more