The class under test looks like:
class State(pivate val repo){
val values = listOf<Int>()
fun update() {
values = repo.generateValues() // <-line 375
}
}
The unit test looks like:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
private class StateTest {
@MockK(relaxed = true) private lateinit var mockedRepo: Repo
@BeforeAll
fun config() {
MockKAnnotations.init(this)
}
@BeforeEach
fun setup() {
clearAllMocks()
unmockkAll()
}
@Test
fun `invoke update`() {
val state = mockk<State>(relaxed = true)
every { state.repo } answers { mockedRepo }
every { mockedRepo.generateValues() } returns listOf(1,2,3)
every { state.update() } answers { callOriginal() }
state.update()
Assertions.assertTrue(state.values.size > 0)
}
}
Runnig the test, a NullPointerException is thrown:
java.lang.NullPointerException
at com.name.someapp.someservice.State.update(State.kt:375)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at io.mockk.proxy.jvm.advice.MethodCall.call(MethodCall.kt:14)
at io.mockk.proxy.jvm.advice.SelfCallEliminatorCallable.call(SelfCallEliminatorCallable.kt:14)
at io.mockk.impl.instantiation.JvmMockFactoryHelper.handleOriginalCall(JvmMockFactoryHelper.kt:95)
at io.mockk.impl.instantiation.JvmMockFactoryHelper.access$handleOriginalCall(JvmMockFactoryHelper.kt:18)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1$invocation$$inlined$stdFunctions$lambda$1.invoke(JvmMockFactoryHelper.kt:27)
at io.mockk.impl.stub.MockKStub$handleInvocation$originalPlusToString$1.invoke(MockKStub.kt:230)
at io.mockk.MockKAnswerScope.callOriginal(API.kt:2205)
CodePudding user response:
The reason you're getting the NullPointerException is that fun update()
does not call getRepo()
(which you've mocked) but instead it uses the backing field repo
directly. (You can see this by compiling the Kotlin source to bytecode and decompiling to Java in IntelliJ IDEA.) This is also documented at kotlinlang.org:
ⓘ On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead.
The answer is, as sidgate
said in the comment, to create a real (not mock) instance of State
and pass the mock repo to it constructor:
val state = State(mockRepo)
It is a code smell to mock the "system under test".