You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MockK测试成员函数时抛出NullPointerException问题排查

MockK测试场景下NullPointerException排查与解决

问题场景

测试State类的update方法时,触发空指针异常,异常指向State.kt第375行的repo.generateValues()调用。

被测类代码

class State(private val repo){
    var values = listOf<Int>() // 注:原代码val无法赋值,修正为var
    fun update() {
        values = repo.generateValues() // <-line 375
    }
}

单元测试代码

@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)
    }
}

异常栈信息

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)

原因分析

  • 私有属性无法被MockK拦截:State类的repo是private val,用mockk<State>创建的Mock对象无法拦截私有属性的访问,当调用callOriginal()执行真实update方法时,repo仍是Mock对象的默认null值,触发空指针。
  • 测试初始化冗余:@BeforeEach中同时调用clearAllMocks()和unmockkAll()属于重复操作,会清空Mock配置,但不是本次空指针的直接原因。
  • 被测类语法错误:原代码中values定义为val却尝试赋值,这是编译期错误,实际运行的代码应该是var,否则无法走到空指针这一步。

解决方法

方案1:直接创建真实State对象(推荐)

不需要MockState本身,直接注入Mock的Repo依赖,这是单元测试的标准做法:

@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() {
        clearMocks(mockedRepo) // 仅清理当前Mock,避免影响其他测试
    }

    @Test
    fun `invoke update`() {
        every { mockedRepo.generateValues() } returns listOf(1,2,3)
        val state = State(mockedRepo) // 注入Mock依赖

        state.update()
        Assertions.assertTrue(state.values.size > 0)
    }
}

方案2:使用Spyk对象替代Mock

如果必须MockState类的部分行为,用spyk创建真实对象的代理,保留原有依赖:

@Test
fun `invoke update`() {
    every { mockedRepo.generateValues() } returns listOf(1,2,3)
    val state = spyk(State(mockedRepo)) // 创建间谍对象,保留真实State的依赖

    state.update()
    Assertions.assertTrue(state.values.size > 0)
}

必要修正:被测类属性定义

确保values是var类型,否则update方法中的赋值操作编译失败:

class State(private val repo){
    var values = listOf<Int>()
    fun update() {
        values = repo.generateValues()
    }
}

内容的提问来源于stack exchange,提问作者r0n9

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 02:09:12