Exceptions and throwables are essential part of many Java APIs. They are useful for modeling application domain layer and controlling program execution flow.

We'll be working on following piece of code:

class CustomException : Exception()

class SystemUnderTest {
    fun doStuff() {
        throw CustomException()
    }
}

Annotated Junit4 method

class AssertionsOnExceptionsTest {
    @Test(expected = CustomException::class)
    fun `it should throw exception`() {
        val systemUnderTest = SystemUnderTest()

        systemUnderTest.doStuff()
    }
}

Not perfect - the whole test method listens for `CustomException` to be thrown anywhere in method - and with junit4 tools we don't have simple way to perform more specific assertion on that exception.

Junit 5 - assertThrows{}

"it should throw exception - junit5"{
        val exception = Assertions.assertThrows(CustomException::class.java) {
            systemUnderTest.doStuff()
        }
        
        Assertions.assertEquals("Something broke!", exception.message)
}

Junit 5 API is more straightforward - it listens for exception to be thrown inside Executable block in assertThrows method. Under the hood it intercepts throwable via try-catch block and throws assertion error if there was no matching exception caught.

Try-catch and runCatching

class AssertionsOnExceptionsSpec : StringSpec({
    val systemUnderTest = SystemUnderTest()

    "it should throw exception - try-catch"{
        var exception: CustomException? = null

        try {
            systemUnderTest.doStuff()
        } catch (e: CustomException) {
            exception = e
        }

        assertNotNull(exception)
    }

    "it should throw exception - runCatching"{
        val exception = kotlin.runCatching {
            systemUnderTest.doStuff()
        }.exceptionOrNull()

        assertNotNull(exception)
    }
})

Try-catch block - standard way to catch exceptions in Java. In this snippet CustomException is caught in catch block, then assigned to mutable variable defined before this block. Not very elegant solution, but it get's the work done.

We can additionally use runCatching function from Kotlin standard library - this function returns Result<T> of given type (in this particular example this would be Unit type) or return Throwable emitted in that block. Then, we can get that exception from result and perform some extra assertions on it.  

Kotest - shouldThrow

class AssertionsOnExceptionsSpec : StringSpec({
    val systemUnderTest = SystemUnderTest()

    "it should throw exception - Kotest: shouldThrow"{
        shouldThrow<CustomException> {
            systemUnderTest.doStuff()
        }
    }
})

In Kotest (KotlinTest) there is already built-in assertion on exception - shouldThrow<>. It does a few things

  • under the hood it uses try-catch block to resolve thrown exception
  • it throws AssertionError if no exception was thrown in that block
  • it handles case where AssertionError would be thrown inside that block
  • it returns exception that was thrown so we are able to perform additional checks
"it should throw exception - kotest: shouldThrow"{
        val exception = shouldThrow<CustomException> {
            systemUnderTest.doStuff()
        }

        exception.message shouldBe "I'm broken"
}

Few things to remember

  • remember that unhandled exception stops test execution
  • don't catch too generic exception - you may swallow AssertionException and get not proper test result

Get new posts directly to your inbox, subscribe to our newsletter

* indicates required

For GDPR compliance, I have to ask you to allow me to send emails to you. I will use your email address to notify you about new content on my site.

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.