我有通过ArgumentCaptor捕获Class参数的问题。我的测试类看起来像这样:
@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);
}
}
当我运行测试时,抛出以下异常:
java.lang.IllegalStateException:classCaptor.capture()不能为null
是否可以在kotlin中使用参数捕获者?
======
更新1:
Kotlin:1.0.0-beta-4584
Mockito:1.10.19
Robolectric:3.0
======
更新2:
堆栈跟踪:
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)
答案 0 :(得分:27)
对于某些人来说,谷歌在Android架构回购中提供了一个文件MockitoKotlinHelpers.kt。它提供了一种方便的方式来调用捕获..只需调用
verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))
答案 1 :(得分:26)
classCaptor.capture()
的返回值为null,但IActivityHandler#navigateTo(Class, Boolean)
的签名不允许空参数。
mockito-kotlin库提供了解决此问题的支持功能。
答案 2 :(得分:4)
如CoolMind in the comment所述,您首先需要为Kotlin-Mockito添加gradle导入,然后转移所有导入以使用此库。您的导入现在将如下所示:
import { Component, TemplateRef } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent{
modalRef: BsModalRef;
config = {
backdrop: true,
ignoreBackdropClick: true
};
constructor(private modalService: BsModalService) {}
person:any={}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template, this.config);
}
openModalWithComponent() {
this.modalRef = this.modalService.show(ModalContentComponent);
this.modalRef.content.title = 'Modal with component';
}
}
/* This is a component which we pass in modal*/
@Component({
selector: 'modal-content',
template: `
<div class="modal-header">
<h4 class="modal-title pull-left">{{title}}</h4>
</div>
<div class="modal-body">
<input type="text" placeholder="Last Name">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" >Save Last Name</button>
</div>
`
})
export class ModalContentComponent {
title: string;
constructor(private modalService: BsModalService) {}
}
然后您的测试课程将是这样的:
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
答案 3 :(得分:3)
根据this solution我的解决方案:
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())
答案 4 :(得分:3)
使用kotlin-mockito https://mvnrepository.com/artifact/com.nhaarman/mockito-kotlin/1.5.0作为依赖项和示例代码,如下所示:
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)
}
答案 5 :(得分:0)
在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
使用方法: (假设我已经模拟了我的存储库并测试了一个viewModel。在使用MenuObject对象调用viewModel的“update()”方法之后,我想确保MenuObject实际上调用了存储库的“updateMenuObject()”方法:
viewModel.update(menuObjectToUpdate)
val arg = getTheArgOfUsedFunctionInMockObject(mockedRepo, mockedRepo::updateMenuObject, MenuObject::class.java)
assertEquals(menuObjectToUpdate, arg)
答案 6 :(得分:0)
您可以为参数捕获器写一个包装器
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
}
}
答案 7 :(得分:0)
另一种方法:
/**
* 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()
)
)