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)