[Tiny] The Story of MockK and ClassCastException

pfilaretov42

Petr Filaretov

Posted on September 5, 2023

[Tiny] The Story of MockK and ClassCastException

Hey folks, today I would like to tell you a story of tests, MockK, and ClassCastException. Hope it will help someone not to spend hours on debugging as I did.

I was merging two microservices into one recently. And one problem I faced was that tests failed with ClassCastException:

java.lang.ClassCastException: class java.lang.Object cannot be cast to class com.example.GraphQLRequest (java.lang.Object is in module java.base of loader 'bootstrap'; com.example.GraphQLRequest is in unnamed module of loader 'app')
Enter fullscreen mode Exit fullscreen mode

Stacktrace and debugging showed that it fails on Kotlin's runBlocking function. Here is a sample class:

class MySecurityManager {
    fun <T> asTechnicalBlocking(block: () -> T): T {
        return runBlocking { internalTechnical(block) }
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

And here is how it was called inside the methods under test:

val request = mySecurityManager.asTechnicalBlocking {
    buildRequest(...)
}
Enter fullscreen mode Exit fullscreen mode

Since two microservices used different versions of Kotlin, coroutines, and Spring Boot, I was playing with versions for some time.

// N hours and M cups of tea later

Then I realised that not all tests fail, which means that something is wrong with the setup for exact test cases.

// X hours and Y cups of tea later

Finally, I found the problem. It was in the declaration of the mock:

class MyTest {
    @MockkBean(relaxed = true)
    lateinit var mySecurityManagerMock: MySecurityManager

    // ...
}
Enter fullscreen mode Exit fullscreen mode

If you write tests with Kotlin, you are most likely familiar with MockK mocking library. We are also using it extensively.

In the code above the mock is declared as relaxed, which means that you can skip specifying behaviour for each method that will be called during the test. Otherwise, if you declare the mock as not relaxed, do not provide behaviour for the method, and this method will be called during the test, the mock will throw an exception.

However, you need to use the relaxed option carefully. Here is what the documentation says: (but who reads it first?)

Note: relaxed mocking is working badly with generic return types. A class cast exception is usually thrown in this case. Opt for stubbing manually in the case of a generic return type.

And this is exactly what happened in my case. If I set relaxed = false on this mock, it will give me a much more clear and expected exception:

  io.mockk.MockKException: no answer found for: MySecurityManager(securityManager bean#6).asTechnicalBlocking(lambda {})
Enter fullscreen mode Exit fullscreen mode

So, use relaxed wisely and keep mocking.


Dream your code, code your dream.

💖 💪 🙅 🚩
pfilaretov42
Petr Filaretov

Posted on September 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related