mockito / mockito-kotlin

Using Mockito with Kotlin
MIT License
3.11k stars 202 forks source link

Stubbing returns null with named argument mixed ordering #268

Open Th4n opened 6 years ago

Th4n commented 6 years ago

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

StormeHawke commented 6 years ago

I'm seeing the same issue

linkrjr commented 5 years ago

Same here. Any ideias?

tim-phillips commented 3 years ago

Just encountered this, very hard to debug.

rasmusandersson5 commented 3 years ago

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

bohsen commented 3 years ago

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.

StormeHawke commented 3 years ago

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

bohsen commented 3 years ago

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

StormeHawke commented 3 years ago

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

tianyanz commented 2 years ago

Is there any plan to fix this?

edwardmp commented 1 year ago

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.

tomas0svk commented 1 year ago

+1

AlexRP239 commented 6 months ago

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.