mockito-kotlin
mockito-kotlin copied to clipboard
Stubbing returns null with named argument mixed ordering
I'm not sure whether it's mockito-kotlin as it seems that something is wrong on boundary of kotlin/mockito, however it might be a thing that mockito-kotlin can solve somehow.
It seems like that using named arguments that are ordered in a different way compared to method signature breaks stubs so they return nulls only. Once I managed to get a class cast exception as Mockito was trying to cast my Arg2 into Arg underneath, however now I'm not able to reproduce it.
Here are couple of tests showing this behaviour
@Test // failing
fun `reverse order stub`() {
val arg = Arg()
val arg2 = Arg2()
val pointFactory: PointFactory = mock()
whenever(pointFactory.newInstance(y = any(), x = any())).thenReturn(mock())
val result = pointFactory.newInstance(arg, arg2)
Truth.assertThat(result).isNotNull()
}
@Test // failing
fun `reverse order stub, reverse invocation`() {
val arg = Arg()
val arg2 = Arg2()
val pointFactory: PointFactory = mock()
whenever(pointFactory.newInstance(y = any(), x = any())).thenReturn(mock())
val result = pointFactory.newInstance(y = arg2, x = arg)
Truth.assertThat(result).isNotNull()
}
@Test
fun `regular order stub, regular invocation`() {
val arg = Arg()
val arg2 = Arg2()
val pointFactory: PointFactory = mock()
whenever(pointFactory.newInstance(x = any(), y = any())).thenReturn(mock())
val result = pointFactory.newInstance(arg, arg2)
Truth.assertThat(result).isNotNull()
}
@Test
fun `regular order stub, reverse invocation`() {
val arg = Arg()
val arg2 = Arg2()
val pointFactory: PointFactory = mock()
whenever(pointFactory.newInstance(x = any(), y = any())).thenReturn(mock())
val result = pointFactory.newInstance(y = arg2, x = arg)
Truth.assertThat(result).isNotNull()
}
class Arg
class Arg2
class Point
class PointFactory {
fun newInstance(x: Arg, y: Arg2): Point {
return Point()
}
}
Also on mockito issue tracker: https://github.com/mockito/mockito/issues/1413
I'm seeing the same issue
Same here. Any ideias?
Just encountered this, very hard to debug.
Yep, this was a nasty one... Makes refactoring quite cumbersome if you can't use named arguments...
I don't think this has been mentioned, but this AFAIK is a java-kotlin interop issue. Kotlin named parameters is a pure Kotlin concept. It is not present in Java which means interoperability is not possible. Mockito-kotlin is still just a wrapper around Mockito(the java library).
Maybe someone with time on their hands should add a section on "Known limitations" to the readme and add this as a gotcha.
You can't use named parameters on Java methods, only on kotlin. It should be possible to rearrange passed parameters the same way kotlin.lang does under the hood to support this
@StormeHawke The problems lies in the stubbing. When you stub a function of a mock, the call can have an arbitrary number of parameters of various types. How would you go about rearranging them? ~~Another issue could be detecting if you're using a matcher or not.~~
As an example look at how whenever is implemented:
/**
* Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
*
* Alias for [Mockito.when].
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
return Mockito.`when`(methodCall)!!
}
whenever delegates the call directly to Mockito.
We're software engineers. If our job was easy anybody could do it 😉
Is there any plan to fix this?
Just spent 2 hours diagnosing why my mock kept returning null. I appreciate it might not be easy to solve but very cumbersome to stumble upon this.
+1
This is quite difficult to fix since mockito stores matchers in the stack as it defined in the when or verify block. And kotlin's variables by name just a syntactic sugar. So you need to always write all parameters (even with default values) in real order as it it in runtime or don't use argument matchers at all.