Unit Testing the ExceptionHandler

Sometimes you just fight against adding SpringBootTest when it comes to asserting the API responses.
– SpringBootTest are slow, they sometimes require to start an entire infrastructure (databases, JMS connections) to only simulate two or three interactions
– You want your tests to be predictive and to run the very method in the controller you need, and nothing else
– You want your tests to be… simple, right ?

I have built a derived version of my controller in the test packages to make sure it uses my ControllerAdvice in a very reliable way.

    val apiController = ApiController(
domainAction1,
domainAction2,
domainAction3,
domainAction4
)

First, prepare your exception handler and declare a fake exception type that will contain the returned error payload of your api.

    val exceptionsHandler = ExceptionsHandler()
data class ExceptionHandledException(val returnedErrorNode: Any?) : RuntimeException()

Just add another tweaked apiController that route your calls through that ExceptionsHandler when things go wrong

val apiWithExceptionsHandler = object : ApiController(
      domainAction1,
      domainAction2,
      domainAction3,
      domainAction4
    ) {
        // can be extended to any method of your controller
        override suspend fun method1(arg1: Arg1, arg2: Arg2) =
            try {
                super.method1(arg1, arg2)
            } catch (e: Throwable) {
                val method = (listOf(e::class) + e::class.supertypes).associateWith { type ->
                    exceptionsHandler::class.declaredFunctions.filter { f ->
                        f.annotations.filterIsInstance()
                            .flatMap { it.value.map { it.java.name } }
                            .contains(if (type is KClass)
                                    (type as KClass).java.name
                            else ((type as KType).classifier as KClass).java.name) }
                }.values.filter { it.isNotEmpty() }.firstOrNull()?.firstOrNull()
                throw ExceptionHandledException(method?.call(exceptionsHandler, e))
            }
        }

Now, when you use apiWithExceptionsHandler, you can do that :

    @Test
    fun `sometimes it goes wrong`() {
        try {
            apiWithExceptionsHandler.method1(
                arg1 = Arg1(), arg2 = Arg2())
        } catch (e: ExceptionHandledException) {
            e.returnedErrorNode.should.equal(ErrorNode(error = VALIDATION_FAILURE,
                errorDescription = "Oops, Arg1 does not comply",
                errorCode = ARG1_DOES_NOT_COMPLY))
        }
    }

You are now able to test your error payloads without needing to write integration tests, comfortable is not it ?
Those tests will run within milliseconds, are easy to debug and easy to extend.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.