mockito / mockito-kotlin

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

OngoingStubbing failing to call doReturn value for covariant types #335

Closed kirillzh closed 5 years ago

kirillzh commented 5 years ago

Code sample:

interface Foo {
  fun bar(): Single<Bar<Int>>
}

sealed class Bar<T> {
  data class A<T>(val a: T) : Bar<T>()
}

class Test {
  val foo = mock<Foo> {
    on { bar() } doReturn Single.just(Bar.A(1))
  }
}

Received error:

error: none of the following functions can be called with the arguments supplied:
public infix fun <T> OngoingStubbing<???>.doReturn(t: ???): OngoingStubbing<???> defined in com.nhaarman.mockito_kotlin
public fun <T> OngoingStubbing<???>.doReturn(t: ???, vararg ts: ???): OngoingStubbing<???> defined in com.nhaarman.mockito_kotlin
public inline infix fun <reified T> OngoingStubbing<Single<Bar<Int>>>.doReturn(ts: List<Single<Bar<Int>>>): OngoingStubbing<Single<Bar<Int>>> defined in com.nhaarman.mockito_kotlin
    on { bar() } doReturn Single.just(Bar.A(1))

Calling thenReturn() from original Mockito library however compiles just fine:

on { bar() }.thenReturn(Single.just(Bar.A(1))

It's seems that the type is getting lost along the way so I tried to define my own infix function with covariant type like so but that gave type mismatch:

infix fun <E> OngoingStubbing<out E>.myReturn(t: E): OngoingStubbing<out E> = thenReturn(t)

I think the problem here is that Single doesn't have covariant type which doesn't allow us to establish relationship between the types. Initial code works as expected if the Bar value is being wrapped in something with covariant types, e.g. Kotlin's List:

interface Foo {
  fun bar(): List<Bar<Int>>
}

sealed class Bar<T> {
  data class A<T>(val a: T) : Bar<T>()
}

class Test {
  val foo = mock<Foo> {
    on { bar() } doReturn listOf(Bar.A(1))
  }
}

I'm not sure if that's org.mockito:mockito-core or com.nhaarman:mockito-kotlin-kt issue.

Mockito: org.mockito:mockito-core:2.7.5 Mockito Kotlin: com.nhaarman:mockito-kotlin-kt1.1:1.5.0 Kotlin: 1.2.71 JDK: 1.8

bohsen commented 5 years ago

@kirillzh Have you tried using onGeneric?

Sry for not being sharp on this.

bohsen commented 5 years ago

This is the correct answer I think:

interface Foo {
    fun bar(): Single<out Bar<Int>>
}

sealed class Bar<T> {
    data class A<T>(val a: T) : Bar<T>()
}

@Test
fun covariantTest() {
    val foo = mock<Foo> {
        on { bar() }.doReturn(Single.just(Bar.A(1)), Single.just(Bar.A(2)))
    }
    val obj = foo.bar().blockingGet()
    val obj2 = foo.bar().blockingGet()
    assertEquals(Bar.A(1), obj)
    assertEquals(Bar.A(2), obj2)
}

You have to change the return type of your function to a covariant type.

nhaarman commented 5 years ago

@kirillzh Did the above solution work for you?

kirillzh commented 5 years ago

Yes, I ended up using Mockito's original thenReturn as a workaround.