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
相关产品推荐
相关产品推荐

