1

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)
r0n9
  • 2,505
  • 1
  • 29
  • 43
  • 2
    if `State` class is under test, why are you instantiating it with mock? why not `State(mockRepo)` ? – sidgate Aug 11 '22 at 08:10
  • The reason I mock the `State` is: that class is not really the thing I am working on. The real class has a long list of arguments in the primary constructor plus dozen of private fields in the initializer block. I do not know how to mock private fields in the initializer block (the mockk doc does not mention and there is a stackover not answered question about that). And there are no existing unit test. I only need to add a new function `update` which changes one private field. So I am trying to avoid to mock other things. Yeah, basically, I am saying I am lazy. – r0n9 Aug 11 '22 at 13:58

1 Answers1

4

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".

Klitos Kyriacou
  • 10,634
  • 2
  • 38
  • 70