假设我的类是:
open class TestThis{
@Autowired
private var myService : MyService? = null
fun doMyFunction(){
val result = myService.doSomething("hello world", Function { entry ->
var retVal : Boolean = false
//some processing
retVal
})
}
}
@Service
open class MyService{
fun doSomething(str1 : String, java.util.Function<MyCrap, Boolean>) : List<String>{
//do something here
}
}
@RunWith(MockitoJUnitRunner::class)
class TestThisTest{
@Mock
var myService : MyService? = null
@InjectMocks
var test : TestThis? = null
@Before
fun before(){
val list : List<String> = //init list
//this line causes compilation error due to generics. error 1
Mockito.`when`(myService.doSomething(Mockito.anyString(), Mockito.any(Function::class.java))).thenReturn(list)
//this line also causes compilation error due to generics. error 2
Mockito.`when`(myService.doSomething(Mockito.anyString(), Mockito.any(Function<MyCrap, Boolean>::class.java))).thenReturn(list)
}
}
错误1:
类型推理失败。预期的类型不匹配。
错误2:
在类文字的左侧只允许使用类
那么,我如何模拟myService#doSomething
?
让匹配器与Kotlin一起工作可能是个问题。
- 如果您有一个用Kotlin编写的方法,它不接受可为null的参数,那么我们无法使用
Mockito.any()
与之匹配。这是因为它返回null
,并且这不可分配给不可为null的参数 - 在Kotlin类和成员默认为final的情况下,我们需要打开它们,因为final方法禁止mocking
- 泛型类模拟上升错误,如您所描述的
Only classes are allowed on the left hand side of a class literal
所有这些问题都可以通过简单的MockitoUtils
类来解决:
object MockitoUtils {
inline fun <reified T> anyObject(): T {
return Mockito.any(T::class.java) ?: createInstance()
}
inline fun <reified T : Any> createInstance(): T {
return when (T::class) {
Boolean::class -> false as T
Byte::class -> 0.toByte() as T
Char::class -> 0.toChar() as T
Short::class -> 0.toShort() as T
Int::class -> 0 as T
Long::class -> 0L as T
Float::class -> 0f as T
Double::class -> 0.0 as T
else -> createInstance(T::class)
}
}
fun <T : Any> createInstance(kClass: KClass<T>): T {
return castNull()
}
@Suppress("UNCHECKED_CAST")
private fun <T> castNull(): T = null as T
}
用法示例:
Mockito.`when`(myService!!.doSomething(Mockito.anyString(), MockitoUtils.anyObject())).thenReturn(list)
模拟服务:
open class MyService {
open fun doSomething(str1 : String, func : java.util.function.Function<MyCrap, Boolean>) : List<String>{
return emptyList()
}
}
测试服务:
open class TestThis{
private var myService : MyService? = null
fun doMyFunction() : List<String>? {
val result = myService?.doSomething("hello world", java.util.function.Function { entry ->
var retVal : Boolean = false
//some processing
retVal
} )
return result;
}
}
测试实施:
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner
import kotlin.reflect.KClass
@RunWith(MockitoJUnitRunner::class)
class TestThisTest{
@Mock
var myService : MyService? = null
@InjectMocks
var test : TestThis? = null
@Before
fun before(){
val list : ArrayList<String> = arrayListOf()
list.add("123")
val thenReturn = Mockito.`when`(myService!!.doSomething(Mockito.anyString(), MockitoUtils.anyObject())).thenReturn(list)
}
@Test
fun test() {
val list = test!!.doMyFunction()
Assert.assertTrue(list!!.contains("123"))
}
object MockitoUtils {
inline fun <reified T> anyObject(): T {
return Mockito.any(T::class.java) ?: createInstance()
}
inline fun <reified T : Any> createInstance(): T {
return when (T::class) {
Boolean::class -> false as T
Byte::class -> 0.toByte() as T
Char::class -> 0.toChar() as T
Short::class -> 0.toShort() as T
Int::class -> 0 as T
Long::class -> 0L as T
Float::class -> 0f as T
Double::class -> 0.0 as T
else -> createInstance(T::class)
}
}
fun <T : Any> createInstance(kClass: KClass<T>): T {
return castNull()
}
@Suppress("UNCHECKED_CAST")
private fun <T> castNull(): T = null as T
}
}
替代解决方案:
另一个可能的解决方案是使用类似mockito kotlin的库。
Maven:
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
你不应该嘲笑"TestThis";当您尝试测试此服务内部的某些内容时。如果你嘲笑它,就什么都没有;内部";。这只是一个模拟。尝试安装它,然后注入MyService的模拟。
你在哪里写测试也很奇怪。为什么在测试类TestThisTest中对MyService进行单元测试。您应该为MyService创建自己的单元测试。