Closed ZOlbrys closed 7 months ago
@TimvdLippe I think this works as expected.
The problem is not in Mockito but in user code.
There's a difference between what doSomethingElse(message, args)
means in Java vs Kotlin.
Here's a repro with 3 classes without using Mockito:
class Tests { // Doesn't matter if test is written in Java or Kotlin
@Test fun kotlin() {
TestClassK().doSomething("message", "arg1")
}
@Test fun java() {
TestClassJ().doSomething("message", "arg1")
}
}
In Java args
is an Object[]
and passing an Object[]
to an Object...
is an exact match. Therefore it just passes the args
array as reference without creating another vararg Object[].
class TestClassJ {
private void doSomethingElse(String message, Object... args) {
System.out.printf("message: %s, args: %s%n", message, Arrays.deepToString(args));
}
void doSomething(String message, Object... args) {
// output: "message: message, args: [arg1]"
doSomethingElse(message, args);
// output: "message: message, args: [[arg1]]"
doSomethingElse(message, (Object) args); // Object is not an array, so will get auto-wrapped to adhere to the `...` method call.
// output: "message: message, args: [[arg1]]"
doSomethingElse(message, new Object[] {args}); // Java needs explicit wrapping.
}
}
In Kotlin args
is Array<Any>
which happens to be also an Any
so the inner vararg happily accepts it (each vararg
creates an array, unless you spread the delegating call).
class TestClassK {
private fun doSomethingElse(message: String, vararg args: Any) {
println("message: $message, args: ${args.contentDeepToString()}")
}
fun doSomething(message: String, vararg args: Any) {
// output: "message: message, args: [[arg1]]"
doSomethingElse(message, args)
// output: "message: message, args: [[arg1]]"
doSomethingElse(message, args as Any)
// output: "message: message, args: [arg1]"
doSomethingElse(message, *args) // Kotlin needs explicitly unwrapping (via spread operator).
}
}
Note that all this is only relevant with Object...
/vararg Any
, if you used any other type you would get a compiler error in both languages. Try replacing every Object
/Any
with String
in the above code. Kotlin will force you to spread and Java won't allow you to explicitly wrap.
@ZOlbrys To explain the diff between 4.10 and 4.11 I think we just need to look at the fixes made in 4.11: https://github.com/mockito/mockito/releases/tag/v4.11.0; based on this I think 4.10 was giving you a false positive and 4.11 is actually highlighting a bug in your code: a missing spread operator:
class TestClass(val service: TestService) {
fun doSomething(message: String, vararg args: Any) {
- service.doSomethingElse(message, args)
+ service.doSomethingElse(message, *args)
}
}
(assuming your test verification expectation is really what you intend the code to do)
Cool, thanks for confirming @TWiStErRob !
@TWiStErRob yeah, that makes total sense! I checked my code and this makes sense.
I appreciate the assistance here, thank you!!
Consider the following code:
With code testing this:
Using
mockito
4.11.0/mockito-kotlin
4.1.0, the test here fails withUsing
mockito
4.10.0, the test above passes successfully. Any ideas?This was also asked on stackoverflow which includes some workarounds that IMO do not make sense to use (I feel like a bug is happening with the code above), but maybe I am mistaken.
After discussion on https://github.com/mockito/mockito/issues/2856 it was requested to move the bug here. In that issue it was discovered that if the source code under test is written in Java and the verification is done using
eq
method such as:verify(mockService).doSomethingElse(eq(message), eq(arg1))
the test passes (using
mockito
directly and/or withmockit-kotlin
). It's only when using kotlin source code that this fails.