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

Mockito Kotlin is breaking with new type inference

Open vRallev opened this issue 5 years ago • 5 comments

I'm referring to https://youtu.be/MyljSWm0Y_k?t=1282 I turned it on this way

    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
        kotlinOptions {
            // progressive helps detecting unstable Kotlin code earlier, see https://blog.jetbrains.com/kotlin/2018/06/kotlin-1-2-50-is-out/
            freeCompilerArgs = ["-Xjsr305=strict", "-progressive", "-XXLanguage:+NewInference", "-XXLanguage:+InlineClasses"]
            allWarningsAsErrors = true
        }
    }

The error is the following

e: /Users/rwondratschek/dev/projects/evernote/evernote/app/src/test/kotlin/com/evernote/EvernoteAppSetupRule.kt: (68, 64): Overload resolution ambiguity:
public inline fun <reified T : Any> mock(extraInterfaces: Array<KClass<out Any>>? = ..., name: String? = ..., spiedInstance: Any? = ..., defaultAnswer: Answer<Any>? = ..., serializable: Boolean = ..., serializableMode: SerializableMode? = ..., verboseLogging: Boolean = ..., invocationListeners: Array<InvocationListener>? = ..., stubOnly: Boolean = ..., @Incubating useConstructor: UseConstructor? = ..., @Incubating outerInstance: Any? = ...): TypeVariable(T) defined in com.nhaarman.mockitokotlin2
public inline fun <reified T : Any> mock(extraInterfaces: Array<KClass<out Any>>? = ..., name: String? = ..., spiedInstance: Any? = ..., defaultAnswer: Answer<Any>? = ..., serializable: Boolean = ..., serializableMode: SerializableMode? = ..., verboseLogging: Boolean = ..., invocationListeners: Array<InvocationListener>? = ..., stubOnly: Boolean = ..., @Incubating useConstructor: UseConstructor? = ..., @Incubating outerInstance: Any? = ..., stubbing: KStubbing<TypeVariable(T)>.(TypeVariable(T)) -> Unit): TypeVariable(T) defined in com.nhaarman.mockitokotlin2
@Deprecated public inline fun <reified T : Any> mock(a: Answer<Any>): TypeVariable(T) defined in com.nhaarman.mockitokotlin2

The issue should be easy to reproduce, basically every call like this fails

        val account = mock<AppAccount> {
            on { databaseHelper } doThrow IllegalStateException()
        }

vRallev avatar Oct 18 '18 19:10 vRallev

I have a similar issue.

verify(someBean).genericMethod(org.mockito.ArgumentMatchers.any()) 
// --> compiles
verify(someBean).genericMethod(com.nhaarman.mockitokotlin2.any())
 // --> Type inference failed: Not enough information to infer parameter T 

Comparing the type signatures:

public static <T> T any()  { ... }
inline fun <reified T : Any> any(): T { ... }

Not that much of a difference. It seems that inline is not ideal here?

(This is with Kotlin 1.2.71.)

reitzig avatar Oct 19 '18 15:10 reitzig

I've got a less cosmetic solution which is to specify the parameter name, so the overload resolution works.

val account = mock<AppAccount>(stubbing = {
// on {} etc...
})

wiyarmir avatar Oct 26 '18 10:10 wiyarmir

This is something that breaks compilation of the library itself as well when -XXLanguage:+NewInference is enabled. Since there isn't any (at least I haven't found any) official documentation or discussion available on this "new type inference" system, it's hard to tell how the Kotlin team will handle issues like these.

One specific case in the library itself is argThat(T.() -> Boolean) and argThat(ArgumentMatcher), which results in the following compilation error:

e: /home/nhaarman/dev/nhaarman/mockito-kotlin/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/Matchers.kt: (93, 12): Overload resolution ambiguity: 
public inline fun <reified T : Any> argThat(noinline predicate: TypeVariable(T).() -> Boolean): TypeVariable(T) defined in com.nhaarman.mockitokotlin2
public inline fun <reified T : Any> argThat(matcher: ArgumentMatcher<TypeVariable(T)>): TypeVariable(T) defined in com.nhaarman.mockitokotlin2

The simplest solution is to remove the custom functions that accept a lambda and let the new type inference system handle this. However, this is a breaking change and will affect users that don't have the new type inference system enabled. Since the -XXLanguage:+NewInference is an internal compiler argument, I'm favoring for not addressing this issue in 2.x.
I know it's a pain for people that do have this flag enabled, but stuff like this is expected for experimental features.

3.x might be able to support his, but for support for the new type inference to have any standing at least some official documentation and stance from the Kotlin team needs to be available, and the feature needs to be stable.

nhaarman avatar Oct 30 '18 08:10 nhaarman

@vRallev @reitzig @wiyarmir Is this still an issue with kotlin 1.3.40-eap-32 where the new type inference is enabled by default?

bohsen avatar May 28 '19 07:05 bohsen

I don't have access anymore to the codebase I observed this in. Now (Kotlin 1.3.40) I have trouble reproducing.

import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import org.junit.Test

interface Mockable {
    fun <T> mockMe(m: T) : String
}

class Foo(val m: Mockable) {
    fun bar() : String = "${m.mockMe("me")}, Amadeus!"
}

internal class SomeTest {
    @Test
    fun testBar() {
        val mock = mock<Mockable> {
            on { mockMe(any<String>()) } doReturn "Mock me"
        }

        val foo = Foo(mock)
        println(foo.bar())
        verify(mock).mockMe(org.mockito.ArgumentMatchers.any())
        verify(mock).mockMe(com.nhaarman.mockitokotlin2.any())
    }
}

Both variants are red.

If the type parameter lives on interface, not method level, both are green:

interface Mockable<T> {
    fun mockMe(m: T) : String
}

class Foo(val m: Mockable<String>) {
    fun bar() : String = "${m.mockMe("me")}, Amadeus!"
}

internal class SomeTest {
    @Test
    fun testBar() {
        val mock = mock<Mockable<String>> {
            on { mockMe(any()) } doReturn "Mock me"
        }

        val foo = Foo(mock)
        println(foo.bar())
        verify(mock).mockMe(org.mockito.ArgumentMatchers.any())
        verify(mock).mockMe(com.nhaarman.mockitokotlin2.any())
    }
}

reitzig avatar Jun 21 '19 20:06 reitzig