web-dev-qa-db-fra.com

kotlin et ArgumentCaptor - IllegalStateException

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)
28
Ramps

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.

24
Mike Buhot

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))

26
Ikechukwu Kalu

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)

        }
2
enes.acikoglu

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())
2
neworld

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
    }
}
0
Amit Kaushik

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)
0
Re'em