称为验证的 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 单元测试方法抛出空指针异常的主要内容,如果未能解决你的问题,请参考以下文章