Petr Filaretov
Posted on September 5, 2023
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')
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) }
}
// ...
}
And here is how it was called inside the methods under test:
val request = mySecurityManager.asTechnicalBlocking {
buildRequest(...)
}
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
// ...
}
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 {})
So, use relaxed
wisely and keep mocking.
Dream your code, code your dream.
Posted on September 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.