android robolectric kotlin

android - kotlin y ArgumentCaptor-IllegalStateException



robolectric (6)

Tengo un problema con la captura del argumento de la clase a través de ArgumentCaptor. Mi clase de prueba se ve así:

@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); } }

Cuando ejecuto la prueba, se lanza la siguiente excepción:
java.lang.IllegalStateException: classCaptor.capture () no debe ser nulo
¿Es posible utilizar argumentos captores en kotlin?

========= ACTUALIZACIÓN 1:
Kotlin: 1.0.0-beta-4584
Mockito: 1.10.19
Robolectric: 3.0

========= ACTUALIZACIÓN 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)


El valor de retorno de classCaptor.capture() es nulo, pero la firma de IActivityHandler#navigateTo(Class, Boolean) no permite un argumento nulo.

La biblioteca mockito-kotlin proporciona funciones de soporte para resolver este problema.


Para algunos, hay un archivo, MockitoKotlinHelpers.kt proporcionado por Google en el repositorio de Arquitectura de Android. Proporciona una forma conveniente de llamar a la captura .. simplemente llame a verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))


Puedes escribir una envoltura sobre argumento captor

class CaptorWrapper<T:Any>(private val captor:ArgumentCaptor<T>, private val obj:T){ fun capture():T{ captor.capture() return obj } fun captor():ArgumentCaptor<T>{ return captor } }


Según esta solución mi solución aquí:

fun <T> uninitialized(): T = null as T //open verificator val verificator = verify(activityHandlerMock) //capture (would be same with all matchers) classCaptor.capture() booleanCaptor.capture() //hack verificator.navigateTo(uninitialized(), uninitialized())


Utilice kotlin-mockito https://mvnrepository.com/artifact/com.nhaarman/mockito-kotlin/1.5.0 como código de ejemplo y dependencia como se indica a continuación:

argumentCaptor<Hotel>().apply { verify(hotelSaveService).save(capture()) assertThat(allValues.size).isEqualTo(1) assertThat(firstValue.name).isEqualTo("Hilton Hotel") assertThat(firstValue.totalRoomCount).isEqualTo(10000L) assertThat(firstValue.freeRoomCount).isEqualTo(5000L) }


Vine aquí después de que la biblioteca kotlin-Mockito no ayudó. He creado una solución utilizando la reflexión. Es una función que extrae el argumento proporcionado al objeto simulado anteriormente:

fun <T: Any, S> getTheArgOfUsedFunctionInMockObject(mockedObject: Any, function: (T) -> S, clsOfArgument: Class<T>): T{ val argCaptor= ArgumentCaptor.forClass(clsOfArgument) val ver = verify(mockedObject) argCaptor.capture() ver.javaClass.methods.first { it.name == function.reflect()!!.name }.invoke(ver, uninitialized()) return argCaptor.value } private fun <T> uninitialized(): T = null as T

Uso: (Diga que me he burlado de mi repositorio y he probado un viewModel. Después de llamar al método "update ()" de viewModel con un objeto MenuObject, quiero asegurarme de que MenuObject haya llamado al método "updateMenuObject ()" del repositorio:

viewModel.update(menuObjectToUpdate) val arg = getTheArgOfUsedFunctionInMockObject(mockedRepo, mockedRepo::updateMenuObject, MenuObject::class.java) assertEquals(menuObjectToUpdate, arg)