IllegalStateException: No lock present for object in Micronaut test with kotest
See original GitHub issuePrerequisites
Please answer the following questions for yourself before submitting an issue.
- I am running the latest version
- I checked the documentation and found no answer
- I checked to make sure that this issue has not already been filed
I’m copying over an issue from Micronaut: https://github.com/micronaut-projects/micronaut-core/issues/3334 .
Expected Behavior
Mockk can be used within Micronaut test using kotest, with different mock behavior per test case.
Current Behavior
In the below example, an ExecutionException
is thrown (stack trace below). Moving the every
clause out of the test case makes the exception go away, but then the mock can only have one behavior per test file, which isn’t very usable.
Commentary from Micronaut devs is: “Seems to be some weird issue where the kotlin mocking library is introducing coroutines into the picture which introduces a concurrency issue where the lock is not visible from the couroutine thread. Why a mocking library needs to introduce concurrency and coroutines a Kotlin expert would need to explain.”
Failure Information (for bugs)
Steps to Reproduce
Run MyServiceTest
in the project linked below for a repro case.
Context
- MockK version: 1.10.1
- Type of test: kotest unit test
Stack trace
No lock present for object: MyClient(#1)
java.lang.IllegalStateException: No lock present for object: MyClient(#1)
at io.micronaut.runtime.context.scope.refresh.RefreshScope.getLock(RefreshScope.java:158)
at io.micronaut.runtime.context.scope.refresh.RefreshInterceptor.intercept(RefreshInterceptor.java:46)
at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:69)
at micronaut.issue.$MyServiceTest$MyClientMock0Definition$Intercepted.getSomeFoos(Unknown Source)
at micronaut.issue.MyService.getFoos(MyService.kt:9)
at micronaut.issue.MyServiceTest$1.invokeSuspend(MyServiceTest.kt:24)
at micronaut.issue.MyServiceTest$1.invoke(MyServiceTest.kt)
at io.kotlintest.runner.jvm.TestCaseExecutor$executeTest$supervisorJob$1$invokeSuspend$$inlined$map$lambda$1.invokeSuspend(TestCaseExecutor.kt:121)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Minimal reproducible code (the gist of this issue)
https://github.com/theHacker/micronaut-issue-notlockpresent
Copying the test case from that link here:
package micronaut.issue
import io.kotlintest.specs.StringSpec
import io.micronaut.test.annotation.MicronautTest
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotlintest.MicronautKotlinTestExtension.getMock
import io.mockk.every
import io.mockk.mockk
@MicronautTest
class MyServiceTest(
private val myClient: MyClient,
private val myService: MyService
) : StringSpec() {
@MockBean(MyClient::class)
fun myClientMock(): MyClient = mockk()
init {
"MyService uses the MyClient" {
every { getMock(myClient).getSomeFoos() } returns listOf(Foo(42, "Foo"))
assert(myService.getFoos().size == 1)
}
}
}
Issue Analytics
- State:
- Created 3 years ago
- Reactions:4
- Comments:5
I realize this is rather old, but given the trouble it took me to track this down, I thought it was worth adding a comment:
One thing that can cause this exception is calling
getMock
for the first time inside the lambda forevery
orcoEvery
(or other setup functions). The reason is that this will trigger the bean being created and used as the key in Micronaut’s hash map for the refresh locks. Since you are in mock set up mode, thehashcode
method (at least in some cases) returns 0 instead of the correct hashcode, which can cause the map entry to be stored in the wrong bucket of the hash map.So, instead of:
You need to do:
I figured it out:
Replace
SomeHttpClient
and remember to usegetMock
in your test suite when accessing the injected variable