Android kotlin和ArgumentCaptor-非法状态例外

Android kotlin和ArgumentCaptor-非法状态例外,android,robolectric,kotlin,Android,Robolectric,Kotlin,我无法通过ArgumentCaptor捕获类参数。我的测试类如下所示: @RunWith(RobolectricGradleTestRunner::class) @Config(sdk = intArrayOf(21), constants = BuildConfig::class) class MyViewModelTest { @Mock lateinit var activityHandlerMock: IActivityHandler; @Captor

我无法通过ArgumentCaptor捕获类参数。我的测试类如下所示:

@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);
    }
}
val mArgumentCaptor = argumentCaptor<SignUpInteractor.Callback>()

@Test
fun signUp_success() {
    val customer = Customer().apply {
        name = "Test Name"
        email = "test@example.com"
        phone = "0123444456789"
        phoneDdi = "+92"
        phoneNumber = ""
        countryCode = "92"
        password = "123456"
    }
    mPresenter.signUp(customer)
    verify(mView).showProgress()
    verify(mInteractor).createAccount(any(), isNull(), mArgumentCaptor.capture())
}

classCaptor.capture()
的返回值为null,但是
IActivityHandler\n navigateTo(类,布尔值)
的签名不允许使用null参数

该库提供了解决此问题的支持功能

代码应为:

    @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);
    }
根据我这里的解决方案:

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())
fun uninitialized():T=null作为T
//开放式验证器
val verificator=验证(activityHandlerMock)
//捕获(与所有匹配器相同)
classCaptor.capture()
booleanCaptor.capture()
//砍
verificator.navigateTo(未初始化(),未初始化())
来自此

“让matchers与Kotlin一起工作可能是一个问题。如果您有一个用Kotlin编写的方法不接受可为null的参数,那么我们无法使用Mockito.any()与之匹配。”。这是因为它可以返回void,并且不能分配给不可为null的参数。如果要匹配的方法是用Java编写的,那么我认为它可以工作,因为所有Java对象都可以隐式为null。”

需要一个包装函数,该函数将
ArgumentCaptor.capture()作为可空类型返回

将以下作为辅助方法添加到测试中

fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()

更新:如果上述解决方案不适用于您,请在下面的评论中查看Roberto Leinardi的解决方案。

在kotlin Mockito库没有帮助后来到这里。 我使用反射创建了一个解决方案。 它是一个函数,用于提取先前提供给模拟对象的参数:

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
使用kotlin mockito作为依赖项,并编写如下示例代码:

argumentCaptor<Hotel>().apply {
    verify(hotelSaveService).save(capture())

    assertThat(allValues.size).isEqualTo(1)
    assertThat(firstValue.name).isEqualTo("İstanbul Hotel")
    assertThat(firstValue.totalRoomCount).isEqualTo(10000L)
    assertThat(firstValue.freeRoomCount).isEqualTo(5000L)
}
argumentCaptor()。应用{
验证(hotelSaveService).save(捕获())
assertThat(allValues.size).isEqualTo(1)
资产(firstValue.name).isEqualTo(“斯坦布尔酒店”)
资产(firstValue.totalRoomCount).isEqualTo(10000L)
资产(firstValue.freeRoomCount).isEqualTo(5000L)
}

您可以在参数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
    }
}
class CaptorWrapper(private val captor:ArgumentCaptor,private val obj:T){
乐趣捕捉():T{
captor.capture()
返回obj
}
fun captor():ArgumentCaptor{
回程俘虏
}
}
如所述,首先需要为添加gradle导入,然后将所有导入转换为使用此库。您的导入现在看起来像:

import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.isNull
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
那么您的测试类将如下所示:

@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);
    }
}
val mArgumentCaptor = argumentCaptor<SignUpInteractor.Callback>()

@Test
fun signUp_success() {
    val customer = Customer().apply {
        name = "Test Name"
        email = "test@example.com"
        phone = "0123444456789"
        phoneDdi = "+92"
        phoneNumber = ""
        countryCode = "92"
        password = "123456"
    }
    mPresenter.signUp(customer)
    verify(mView).showProgress()
    verify(mInteractor).createAccount(any(), isNull(), mArgumentCaptor.capture())
}
val mArgumentCaptor=argumentCaptor()
@试验
有趣的注册(成功){
val customer=customer()。应用{
name=“测试名称”
电子邮件=”test@example.com"
电话=“0123444456789”
phoneDdi=“+92”
phoneNumber=“”
countryCode=“92”
密码=“123456”
}
mPresenter.注册(客户)
验证(mView.showProgress())
验证(mInteractor).createAccount(any()、isNull()、mArgumentCaptor.capture())
}
另一种方法:

/**
 * Use instead of ArgumentMatcher.argThat(matcher: ArgumentMatcher<T>)
 */
fun <T> safeArgThat(matcher: ArgumentMatcher<T>): T {
    ThreadSafeMockingProgress.mockingProgress().argumentMatcherStorage
        .reportMatcher(matcher)
    return uninitialized()
}

@Suppress("UNCHECKED_CAST")
private fun <T> uninitialized(): T = null as T

刚试过,效果很好。您使用的kotlin、mockito和roboelectric的版本是什么?谢谢您的检查。我已经更新了问题。您尝试了哪些版本?只需重新检查,就可以与您列出的版本配合使用。似乎您的代码一定有所不同。你能发布stacktrace吗?我已经添加了stacktrace。不幸的是,它似乎没有什么帮助。是的,就是这样。你是对的。我没有注意到navigateTo方法只接受非null值。当我将navigateTo方法的信号改为:funNavigateTo(clazz:Class?,closeCurrent:Boolean)时,这个示例工作得很好。如果您愿意,请为这个问题添加一个答案,这样,如果其他人有相同的问题,他就可以找到解决方案。只需为搜索此问题的其他人添加上述答案:您需要的是com.nhaarman.mockito_kotlin.capture(classCaptor)而不是classCaptor.capture()谷歌代码实验室的测试结果是这样的:CaptorI曾多次尝试应用com.nhaarman.mockito_kotlin.capture,但未能成功。看见有一个指向wiki的链接:。因此,我尝试从导入中删除几个Mockito*库,将
verify
更改为com.nhaarman.Mockito_kotlin.verify,问题消失了。我必须明确指定T类型不应为null:
com.nhaarman.mockitokotlin2.capture(argumentCaptor)
@CoolMind这很有帮助。谢谢对于其他人,请明确说明要做什么。谢谢,这对我很有用。GoogleSample中关于UnitTest的部分非常值得学习!我很高兴这对你有用,angryd。当然我基本上是在遇到困难时参考他们的样品,而且有很好的记录。我认为这应该是公认的答案。使用fun capture(captor:ArgumentCaptor)帮助我不必添加mockito kotlin:添加后我仍然有相同的问题:
fun capture(ArgumentCaptor:ArgumentCaptor):T=ArgumentCaptor.capture()
然后在测试中:
val userCacheCaptor=ArgumentCaptor.forClass(User::class.java)mockito.verify(userCache,Mockito.times(1)).set(Mockito.anyString(),capture(userCacheCaptor))
错误是:
java.lang.IllegalStateException:capture(userCacheCaptor)不能为null
@IkechukwuKalu我遇到的问题与我使用注释
@Captor private lateinit var snackbarManagerArgumentCaptor:ArgumentCaptor解决的问题相同,然后使用助手
验证(snackbarManager).show(捕获(snackbarManagerArgumentCaptor))
。我不知道为什么,但这是迄今为止对我有效的唯一解决方案。虽然从技术上讲,其他解决方案也应该有效,因为它们似乎只需要将null转换为T。
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.isNull
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
val mArgumentCaptor = argumentCaptor<SignUpInteractor.Callback>()

@Test
fun signUp_success() {
    val customer = Customer().apply {
        name = "Test Name"
        email = "test@example.com"
        phone = "0123444456789"
        phoneDdi = "+92"
        phoneNumber = ""
        countryCode = "92"
        password = "123456"
    }
    mPresenter.signUp(customer)
    verify(mView).showProgress()
    verify(mInteractor).createAccount(any(), isNull(), mArgumentCaptor.capture())
}
/**
 * Use instead of ArgumentMatcher.argThat(matcher: ArgumentMatcher<T>)
 */
fun <T> safeArgThat(matcher: ArgumentMatcher<T>): T {
    ThreadSafeMockingProgress.mockingProgress().argumentMatcherStorage
        .reportMatcher(matcher)
    return uninitialized()
}

@Suppress("UNCHECKED_CAST")
private fun <T> uninitialized(): T = null as T
verify(spiedElement, times(1)).method(
    safeArgThat(
        CustomMatcher()
    )
)