mockito-kotlin icon indicating copy to clipboard operation
mockito-kotlin copied to clipboard

Stubbing returns null with named argument mixed ordering

Open Th4n opened this issue 7 years ago • 12 comments
trafficstars

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

Th4n avatar Jun 25 '18 10:06 Th4n

I'm seeing the same issue

StormeHawke avatar Sep 27 '18 16:09 StormeHawke

Same here. Any ideias?

linkrjr avatar Jan 17 '19 23:01 linkrjr

Just encountered this, very hard to debug.

tim-phillips avatar Jan 05 '21 00:01 tim-phillips

Yep, this was a nasty one... Makes refactoring quite cumbersome if you can't use named arguments...

rasmusandersson5 avatar Mar 19 '21 07:03 rasmusandersson5

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.

bohsen avatar Mar 19 '21 07:03 bohsen

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 avatar Mar 19 '21 11:03 StormeHawke

@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.

bohsen avatar Mar 19 '21 12:03 bohsen

We're software engineers. If our job was easy anybody could do it 😉

StormeHawke avatar Mar 19 '21 12:03 StormeHawke

Is there any plan to fix this?

tianyanz avatar Feb 17 '22 23:02 tianyanz

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.

edwardmp avatar Aug 16 '23 19:08 edwardmp

+1

tomas0svk avatar Sep 20 '23 11:09 tomas0svk

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.

AlexRP239 avatar Apr 09 '24 06:04 AlexRP239