J'ai un problème avec la capture de l'argument de la classe via ArgumentCaptor. Ma classe de test ressemble à ceci:
@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);
}
}
Lorsque j'exécute le test, l'exception suivante est levée:
Java.lang.IllegalStateException: classCaptor.capture () ne doit pas être null
Est-il possible d’utiliser des capteurs d’arguments dans kotlin?
========= UPDATE 1:
Kotlin: 1.0.0-beta-4584
Mockito: 1.10.19
Robolectric: 3,0
========= UPDATE 2:
Trace de la pile:
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)
La valeur de retour de classCaptor.capture()
est null, mais la signature de IActivityHandler#navigateTo(Class, Boolean)
ne permet pas un argument nul.
La bibliothèque mockito-kotlin fournit des fonctions de support pour résoudre ce problème.
Pour certains, il existe un fichier - MockitoKotlinHelpers.kt fourni par Google dans le référentiel Android Architecture. Il constitue un moyen pratique d’appeler la capture .. appelez simplement verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))
Utilisez kotlin-mockito https://mvnrepository.com/artifact/com.nhaarman/mockito-kotlin/1.5.0 comme dépendance et un exemple de code, comme indiqué ci-dessous:
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)
}
Selon cette solution ma solution ici:
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())
Vous pouvez écrire un wrapper over captor argument
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
}
}
Nous sommes venus ici après que la bibliothèque Kotlin-Mockito n'ait pas aidé. J'ai créé une solution en utilisant réflexion . C'est une fonction qui extrait l'argument fourni à l'objet mocked précédemment:
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
Utilisation: (Supposons que je me suis moqué de mon référentiel et que j'ai testé un viewModel. Après avoir appelé la méthode "update ()" de viewModel avec un objet MenuObject, je veux m'assurer que MenuObject a effectivement appelé la "updateMenuObject ()" du référentiel méthode:
viewModel.update(menuObjectToUpdate)
val arg = getTheArgOfUsedFunctionInMockObject(mockedRepo, mockedRepo::updateMenuObject, MenuObject::class.Java)
assertEquals(menuObjectToUpdate, arg)