称为验证的 Mockito Kotlin 单元测试方法抛出空指针异常

Posted

技术标签:

【中文标题】称为验证的 Mockito Kotlin 单元测试方法抛出空指针异常【英文标题】:Mockito Kotlin Unit Testing Method called verification throws Null Pointer Exception 【发布时间】:2019-02-15 20:05:30 【问题描述】:

每当我尝试验证方法是否被调用时,我都会收到Null Pointer Exception

class NotesDialogPagePresenterTest 

@Mock
private lateinit var repository: OrderSummaryRepository
@Mock
private lateinit var view: NotesDialogPageContract.View
@Mock
private lateinit var context: Context

private lateinit var presenter: NotesDialogPagePresenter
private val notes = "abcd"
private val remarks = "xyz"


@Before
fun setup() 
    MockitoAnnotations.initMocks(this)
    whenever(view.getContext()).thenReturn(context)
    this.presenter = NotesDialogPagePresenter(view, repository)


@Test
fun onCompleteCallClicked_successTest() 

    whenever(repository.updateOrderAfterCompleteCall(notes, remarks)).thenReturn(Completable.complete())

    val spyPresenter = Mockito.spy(presenter)
    spyPresenter.onCompleteCallClicked(notes, remarks)

    verify(view, times(1)).showOnCompleteCallSuccess()
    verify(spyPresenter, times(1)).updateUser()//Null Pointer Exception Here

演讲者班

 class NotesDialogPagePresenter @Inject constructor(var view: NotesDialogPageContract.View,
                                               var repository: OrderSummaryRepository)
: NotesDialogPageContract.Presenter 

private var disposable: Disposable? = null

override fun start() 


override fun onCompleteCallClicked(notes: String, remarks: String) 

    disposable = repository.updateOrderAfterCompleteCall(notes, remarks)
            .subscribe(
                view.showOnCompleteCallSuccess()
                updateUser() 
            , 
                view.showError(it)
            )


@VisibleForTesting
internal fun updateUser() 
    disposable = repository.updateUser(-1)
            .subscribe(
                //NO-OP
            , 
                //NO-OP
            )


override fun stop() 
    disposable?.dispose()

这两个方法需要验证,updateUser()会抛出Null 指针异常

view.showOnCompleteCallSuccess() updateUser()

Logcat:

java.lang.NullPointerException
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.updateUser$app_pgDebug(NotesDialogPagePresenter.kt:30)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter$onCompleteCallClicked$1.run(NotesDialogPagePresenter.kt:22)
    at io.reactivex.internal.observers.CallbackCompletableObserver.onComplete(CallbackCompletableObserver.java:54)
    at io.reactivex.internal.disposables.EmptyDisposable.complete(EmptyDisposable.java:68)
    at io.reactivex.internal.operators.completable.CompletableEmpty.subscribeActual(CompletableEmpty.java:27)
    at io.reactivex.Completable.subscribe(Completable.java:1794)
    at io.reactivex.Completable.subscribe(Completable.java:1860)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.onCompleteCallClicked(NotesDialogPagePresenter.kt:20)
    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 com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.onCompleteCallClicked(NotesDialogPagePresenter.kt:19)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenterTest.onCompleteCallClicked_successTest(NotesDialogPagePresenterTest.kt:42)
    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 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    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 com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)
Exception in thread "main" java.lang.NullPointerException
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.updateUser$app_pgDebug(NotesDialogPagePresenter.kt:30)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter$onCompleteCallClicked$1.run(NotesDialogPagePresenter.kt:22)
    at io.reactivex.internal.observers.CallbackCompletableObserver.onComplete(CallbackCompletableObserver.java:54)
    at io.reactivex.internal.disposables.EmptyDisposable.complete(EmptyDisposable.java:68)
    at io.reactivex.internal.operators.completable.CompletableEmpty.subscribeActual(CompletableEmpty.java:27)
    at io.reactivex.Completable.subscribe(Completable.java:1794)
    at io.reactivex.Completable.subscribe(Completable.java:1860)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.onCompleteCallClicked(NotesDialogPagePresenter.kt:20)
    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 com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenter.onCompleteCallClicked(NotesDialogPagePresenter.kt:19)
    at com.rosia.bcp.ordersummary.notes.NotesDialogPagePresenterTest.onCompleteCallClicked_successTest(NotesDialogPagePresenterTest.kt:42)
    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 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    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 com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

仅供参考:我正在使用以下依赖项:

testImplementation 'org.mockito:mockito-core:2.19.0'
testImplementation 'com.squareup.retrofit2:retrofit-mock:2.3.0'
testImplementation 'org.mockito:mockito-inline:2.8.9'
testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.6.0'
testImplementation 'junit:junit:4.12'

【问题讨论】:

【参考方案1】:

我遇到了类似的问题,结果发现要模拟的方法应该标记为open

下面是一个有效的代码示例:

class ToBeMocked 
    open fun myName() = "ToBeMocked"


class MyClass(val it: ToBeMocked)  
   fun myName() = it.myName()


class MyClassTest 
    val myMock: ToBeMocked = mock()
    
    val sut = MyClass(myMock)

    @Test
    fun test() 
        whenever(myMock.myName()).thenReturn("Test")
        assertThat(sut.myName()).equalsTo("Test")
    

【讨论】:

【参考方案2】:

如果您不想模拟updateUser() 的输出,您可以实现一个演示者接口类并验证是否调用了演示者接口函数。

第 1 步:添加演示者接口类

interface INotesDialogPagePresenter 
   fun updateUser()

第 2 步:为演示者实现接口类

class NotesDialogPagePresenter @Inject constructor(var view:NotesDialogPageContract.View,var repository: OrderSummaryRepository)
: NotesDialogPageContract.Presenter,INotesDialogPagePresenter 

private var disposable: Disposable? = null
var implNotesDialogPagePresenter :INotesDialogPagePresenter = this

override fun start() 


override fun onCompleteCallClicked(notes: String, remarks: String) 

    disposable = repository.updateOrderAfterCompleteCall(notes, remarks)
            .subscribe(
                view.showOnCompleteCallSuccess()
                implNotesDialogPagePresenter.updateUser() 
            , 
                view.showError(it)
            )


override fun updateUser() 
    disposable = repository.updateUser(-1)
            .subscribe(
                //NO-OP
            , 
                //NO-OP
            )


override fun stop() 
    disposable?.dispose()

第 3 步:更新您的测试类(使用 mockito-kotlin)

class NotesDialogPagePresenterTest 

val view:NotesDialogPageContract.View= mock()
val repository:OrderSummaryRepository= mock()
val context:Context= mock()
val implNotesDialogPagePresenter :INotesDialogPagePresenter = mock()

private lateinit var presenter: NotesDialogPagePresenter
private val notes = "abcd"
private val remarks = "xyz"


@Before
fun setup() 
    whenever(view.getContext()).thenReturn(context)
    this.presenter = NotesDialogPagePresenter(view, repository)
    presenter.implNotesDialogPagePresenter = implNotesDialogPagePresenter 


@Test
fun onCompleteCallClicked_successTest() 

    whenever(repository.updateOrderAfterCompleteCall(notes, remarks)).thenReturn(Completable.complete())

    val spyPresenter = Mockito.spy(presenter)
    spyPresenter.onCompleteCallClicked(notes, remarks)

    verify(view, times(1)).showOnCompleteCallSuccess()
    verify(implNotesDialogPagePresenter , times(1)).updateUser()

这样,您实际上不会运行 updateUser(),而是只需验证模拟的接口对象是否被调用的乐趣。这可以防止空指针并增加您的测试覆盖率。

【讨论】:

所以你基本上想说,我需要在presenter 上实现接口并在presenter 本身内部调用相同的method,即INotesDialogPagePresenter 没错。这样,您实际上可以验证是否调用了 updateuser。这里的另一个解决方案建议在调用 updateuser 时模拟它的响应,这是通过测试所必需的。但是你实际上无法验证是否调用了该方法。【参考方案3】:

由于您在模拟 OrderSummaryRepository,因此您必须模拟消费类的每个方法调用,即在这种情况下 NotesDialogPagePresenter 调用。正如 Chris 指出的那样,您必须为 updateUser() 添加一个模拟

repository.updateUser(anyInt()) doReturn any()

【讨论】:

【参考方案4】:

您不是在模拟 updateUser 调用。

您需要在测试设置中添加类似以下内容,具体取决于 updateUser 预期返回的内容。

whenever(repository.updateUser(-1)).thenReturn(Completable.complete())

【讨论】:

为什么我需要模拟方法调用只是为了验证它是否被调用?我没有为updateUser() 执行任何进一步的操作。所以,我想没有必要模拟 updateUser() 调用 您的演示者调用 onCompleteCallClicked,这调用 showOnCompleteCallSuccess,然后调用 updateUser。 updateUser 默认为 null,因为它没有被模拟。如果您对在测试中抛出 NullPointerException 感到高兴,即您不想为 updateUser 提供值,则可以使用预期参数告诉测试预期异常:@Test(expected = NullPointerException.类) 添加 @Test(expected = NullPointerException.class) 会引发与上述相同的错误。 如果 NullPointerException 没有通过测试,那么您可能不需要做任何事情,如果抛出异常,添加预期将告诉测试通过,它不会阻止异常发生在您的演示者身上。在测试中停止 NullPointerException 的唯一方法是模拟 updateUser 方法。 好的,谢谢。但我想这是Mockito-Kotlin 的问题。我也在图书馆上发布了这个问题。

以上是关于称为验证的 Mockito Kotlin 单元测试方法抛出空指针异常的主要内容,如果未能解决你的问题,请参考以下文章

使用 Mockito 为 Firebase 用户身份验证设置单元测试

学习笔记单元测试之mockito学习笔记

学习单元测试 Mockito 一篇文章就够了

学习笔记单元测试之mockito学习笔记

学习笔记单元测试之mockito学习笔记

Android单元测试系列-Mock之Mockito