Suppose my class is:
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)
}
}
error 1:
Type inference failed. Expected type mismatch.
error 2:
Only classes are allowed on the left hand side of a class literal
So, how do I mock myService#doSomething
?
CodePudding user response:
Getting matchers to work with Kotlin can be a problem.
- If you have a method written in Kotlin that does not take a nullable parameter then we cannot match with it using
Mockito.any()
. This is because it returnsnull
and this is not assignable to a non-nullable parameter. - in Kotlin classes and members are final by default, we need to open them, because mocking is prohibit for final methods
- Generic classes mocks rise errors like you described
Only classes are allowed on the left hand side of a class literal
All these problems can be resolved via simple MockitoUtils
class:
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
}
Example of usage:
Mockito.`when`(myService!!.doSomething(Mockito.anyString(), MockitoUtils.anyObject())).thenReturn(list)
Service to mock:
open class MyService {
open fun doSomething(str1 : String, func : java.util.function.Function<MyCrap, Boolean>) : List<String>{
return emptyList()
}
}
Service to test:
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;
}
}
Test impplementation:
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
}
}
Alternative solution:
Another possible solution would be to use a library like mockito-kotlin.
Maven:
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
CodePudding user response:
You should not mock "TestThis" when you try to test something inside this Service. If you mock it there is nothing "inside". It is just a mock. Try instanciating it and then inject a mock of MyService.
It also seems weird where you are writing your Test. Why is a Unit test for MyService in the testclass TestThisTest. You should creat your own Unit test for MyService.