I have a problem with capturing the Class argument via ArgumentCaptor. My test class looks like this:
@RunWith(RobolectricGradleTestRunner::class) @Config(sdk = intArrayOf(21), constants = BuildConfig::class) class MyViewModelTest { @Mock lateinit var activityHandlerMock: IActivityHandler; @Captor lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>> @Captor lateinit var booleanCaptor: ArgumentCaptor<Boolean> private var objectUnderTest: MyViewModel? = null @Before fun setUp() { initMocks(this) ... objectUnderTest = MyViewModel(...) } @Test fun thatNavigatesToAddListScreenOnAddClicked(){ //given //when objectUnderTest?.addNewList() //then verify(activityHandlerMock).navigateTo(classCaptor.capture(), booleanCaptor.capture()) var clazz = classCaptor.value assertNotNull(clazz); assertFalse(booleanCaptor.value); } }
When I run the test, following exception is thrown:
java.lang.IllegalStateException: classCaptor.capture() must not be null
Is it possible to use argument captors in kotlin?
========= UPDATE 1:
Kotlin: 1.0.0-beta-4584
Mockito: 1.10.19
Robolectric: 3.0
========= UPDATE 2:
Stacktrace:
java.lang.IllegalStateException: classCaptor.capture() must not be null at com.example.view.model.ShoplistsViewModelTest.thatNavigatesToAddListScreenOnAddClicked(ShoplistsViewModelTest.kt:92) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) 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.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54) 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.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152) 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:69) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
From this blog
"Getting matchers to work with Kotlin can be a problem. If you have a method written in kotlin that does not take a nullable parameter then we cannot match with it using Mockito.any(). This is because it can return void and this is not assignable to a non-nullable parameter. If the method being matched is written in Java then I think that it will work as all Java objects are implicitly nullable."
A wrapper function is needed that returns ArgumentCaptor.capture()
as nullable type.
Add the following as a helper method to your test
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
Please see, MockitoKotlinHelpers.kt provided by Google in the Android Architecture repo for reference. the capture
function provides a convenient way to call ArgumentCaptor.capture()
. Call
verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))
Update: If the above solution does not work for you, please check Roberto Leinardi's solution in the comments below.
The return value of classCaptor.capture()
is null, but the signature of IActivityHandler#navigateTo(Class, Boolean)
does not allow a null argument.
The mockito-kotlin library provides supporting functions to solve this problem.
Code should be:
@Captor lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>> @Captor lateinit var booleanCaptor: ArgumentCaptor<Boolean> ... @Test fun thatNavigatesToAddListScreenOnAddClicked(){ //given //when objectUnderTest?.addNewList() //then verify(activityHandlerMock).navigateTo( com.nhaarman.mockitokotlin2.capture<Class<BaseActivity>>(classCaptor.capture()), com.nhaarman.mockitokotlin2.capture<Boolean>(booleanCaptor.capture()) ) var clazzValue = classCaptor.value assertNotNull(clazzValue); val booleanValue = booleanCaptor.value assertFalse(booleanValue); }
OR
var classCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Class<BaseActivity>>() var booleanCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Boolean>() ... verify(activityHandlerMock).navigateTo( classCaptor.capture(), booleanCaptor.capture() )
also in build.gradle add this:
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With