Given the following class (written in kotlin):
class Target {
fun <R> target(filter: String, mapper: (String) -> R): R = mapper(filter)
}
I'm able to test in java, the test code:
@Test
public void testInJava() {
Target mockTarget = Mockito.mock(Target.class);
Mockito.when(mockTarget.target(
argThat(it -> true),
Mockito.argThat(it -> true)
)).thenReturn(100);
assert mockTarget.target("Hello World", it -> 1) == 100;
}
The java test pass as expected, but the same test is written in kotlin:
@Test
fun test() {
val mockTarget = Mockito.mock(Target::class.java)
Mockito.`when`(mockTarget.target(
Mockito.argThat<String> { true },
mapper = Mockito.argThat<Function1<String, Int>>({ true }))
).thenReturn(100)
assert(mockTarget.target("Hello World") { 1 } == 100)
}
The kotlin version I receive the following exception:
java.lang.IllegalStateException: Mockito.argThat<String> { true } must not be null
Why is it happening and how can I test that using kotlin?
In 2022, Mockito-Kotlin officially solves the problem.
The fix is very simple: Just import the argThat
/eq
/... from the mockito-kotlin
package, instead of the mockito
package, and everything is done!
Related: https://github.com/mockito/mockito-kotlin/wiki/Mocking-and-verifying
As of this writing, mockito-kotlin hasn't been updated for more than a year. As with all of these libraries, there's always a constant need for keeping them up-to-date, and I didn't want to get stuck with an unmaintained library.
So I came up with another way to solve the null issue with argThat
without using any other libraries.
Say we've an interface UuidRepository
as follows:
interface UuidRepository {
suspend fun Entity save(entity: Entity): Entity
}
class Entity
has two properties, userId: String
and uuid: String
.
The following code fails:
Mockito.verify(uuidRepository).save(argThat { it.userId == someValue && it.uuid == "test" })
with the error:
argThat { it.userId == someValue && it.uuid == "test" } must not be null
To solve this, we get all the invocation on the mock and then verify the ones we want:
val invocations = Mockito.mockingDetails(uuidRepository).invocations
.filter { setOf("findById", "save").contains(it.method.name) }
.map { it.method.name to it.arguments }
.toMap()
assertThat(invocations).containsKey("save")
val first = invocations["save"]?.first()
assertThat(first).isNotNull
val entity = first as Entity
assertThat(entity.userId).isEqualTo(someValue)
assertThat(entity.uuid).isEqualTo("test")
I also faced the same problem.
And finally, I found argThat()
will return null, and normally the argument in the function in kotlin, does not accept null type.
The source code of argThat
from ArgumentMatchers.java
public static <T> T argThat(ArgumentMatcher<T> matcher) {
reportMatcher(matcher);
return null;
}
You can see that it return null
. So when we mock the function, it will throw IllegalStateException, because argThat
returns null and argument can't be null.
It mean that if your function is:
fun doSomething(arg1: String): Int {
// do something
}
When you mock it like that:
Mockito.`when`(
doSomething(Mockito.argThat<String> { true })
).thenReturn(100)
It will throw IllegalStateException
So you should change your function like that:
fun doSomething(arg1: String?): Int {
// do something
}
Change the "String" to "String?", make it accept null type.
My solution is to define the argument with class? so that it can accept null, but I don't know if it is a great solution
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With