【问题标题】:Mockito Kotlin Unit Testing Method called verification throws Null Pointer Exception称为验证的 Mockito Kotlin 单元测试方法抛出空指针异常
【发布时间】: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'

【问题讨论】:

    标签: android unit-testing kotlin nullpointerexception mockito


    【解决方案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 的问题。我也在图书馆上发布了这个问题。
        猜你喜欢
        • 2021-05-02
        • 1970-01-01
        • 2019-04-09
        • 1970-01-01
        • 2017-11-17
        • 1970-01-01
        • 2013-02-15
        • 2021-09-17
        相关资源
        最近更新 更多