Mocking a service that uses a context receiver Raise using Mockito

huangapple go评论59阅读模式
英文:

Mocking a service that uses a context receiver Raise<BusinessError> using Mockito

问题

Here's the translation of the provided text:

在Github上可以找到源代码:https://github.com/codependent/context-receiver-sample

假设您正在测试一个名为`ServiceOne`的服务,该服务依赖于`ServiceTwo`。

`ServiceTwo`有一个带有上下文接收器`Raise&lt;BusinessError&gt;`的方法`call()`:

```kotlin
class ServiceOne(private val serviceTwo: ServiceTwo) {

    fun call(code: String): String {
        val result = either {
            val r = serviceTwo.call(code)
            r
        }

        return result.fold(
            { "-1" }){ it }
    }
}
class ServiceTwo {

    context(Raise&lt;BusinessError&gt;)
    fun call(id: String): String {
        return id
    }

}
sealed class BusinessError {
    object SomeError: BusinessError()
}

我想要使用Mockito模拟对ServiceTwo的依赖关系。为了做到这一点,当模拟时,我需要为其上下文接收器提供一个上下文,因此我将其包装在一个either块中:

class ServiceOneTests {


    @Test
    fun `should return the correct aggregation result`() {

        val serviceTwo = mock(ServiceTwo::class.java)
        val serviceOne = ServiceOne(serviceTwo)

        val result = either {
            `when`(serviceTwo.call("1")).thenReturn("1")

            val mockResult = serviceTwo.call("1")
            assertEquals("1", mockResult)

            serviceOne.call("1")
        }

        when(result){
            is Either.Left -> fail()
            is Either.Right -> assertEquals("1", result.value)
        }
    }

}

执行测试时,可以看到mockResult,即serviceTwo.call(code)已经正确模拟,具有值"1"。

然而,这个测试最终失败,因为serviceOne.call("1")中的val r = serviceTwo.call(code)中的r为null。

我怀疑这是因为在模拟设置期间使用的上下文(either {})与在fun serviceOne.call()中建立的上下文不同,后者有自己的either {}块。

您如何修复这个问题,以便模拟调用不会返回null?


<details>
<summary>英文:</summary>

Source code available on Github: https://github.com/codependent/context-receiver-sample

Suppose you&#39;re testing a service `ServiceOne` that has a dependency on `ServiceTwo`. 

`ServiceTwo` has a method `call()` with a context receiver of `Raise&lt;BusinessError&gt;`:

class ServiceOne(private val serviceTwo: ServiceTwo) {

fun call(code: String): String {
    val result = either {
        val r = serviceTwo.call(code)
        r
    }

    return result.fold(
        { &quot;-1&quot; }){ it }
}

}

class ServiceTwo {

context(Raise&lt;BusinessError&gt;)
fun call(id: String): String {
    return id
}

}

sealed class BusinessError {
object SomeError: BusinessError()
}


I&#39;d like to mock the dependency on `ServiceTwo` using Mockito. In order to do so, when mocking I need to provide a context for its context receiver so I wrapped it with an either block:

class ServiceOneTests {

@Test
fun `should return the correct aggregation result`() {

    val serviceTwo = mock(ServiceTwo::class.java)
    val serviceOne = ServiceOne(serviceTwo)

    val result = either {
        `when`(serviceTwo.call(&quot;1&quot;)).thenReturn(&quot;1&quot;)

        val mockResult = serviceTwo.call(&quot;1&quot;)
        assertEquals(&quot;1&quot;, mockResult)

        serviceOne.call(&quot;1&quot;)
    }

    when(result){
        is Either.Left -&gt; fail()
        is Either.Right -&gt; assertEquals(&quot;1&quot;, result.value)
    }
}

}


When executing the test, you can see that `mockResult`, which is `serviceTwo.call(code)` has been correctly mocked, having a &quot;1&quot; value.

However, this test ends up failing because `r` in `val r = serviceTwo.call(code)` in `serviceOne.call(&quot;1&quot;)` is null.

I suspect it must be because the context (`either {}`) used during the mock setup is different from the context established in `fun serviceOne.call()`, which has it&#39;s own `either {}` block.

How would you fix this so the mocked call doesn&#39;t return null?

</details>


# 答案1
**得分**: 2

I suspect it must be because the context (either {}) used during the mock setup is different from the context established in fun serviceOne.call(), which has its own either {} block.

How would you fix this so the mocked call doesn't return null?

This is indeed because Mockito only mocks for the _exact_ instance passed of `Either`, and you should use an argument matcher instead.

You can do this by casting to the underlying signature and passing an explicit matcher for the context receiver.

```kotlin
val serviceTwo = mock(ServiceTwo::class.java)
val serviceOne = ServiceOne(serviceTwo)

`when`(serviceTwo::call.invoke(anyObject(), eq("1"))).thenReturn("1")

I had to define a helper to fix the non-null issue of Mockito, without bringing in mockito-kotlin.

object MockitoHelper {
    fun <T> anyObject(): T {
        Mockito.any<T>()
        return uninitialized()
    }
    @Suppress("UNCHECKED_CAST")
    fun <T> uninitialized(): T = null as T
}

Perhaps MockK will add support for this in the future, but I would recommend using interfaces and stubs instead of mocking. This would heavily simplify this pattern.

英文:

> I suspect it must be because the context (either {}) used during the mock setup is different from the context established in fun serviceOne.call(), which has it's own either {} block.

> How would you fix this so the mocked call doesn't return null?

This is indeed because Mockito only mocks for the exact instance passed of Either, and you should use an argument matcher instead.

You can do this by casting to the underlying signature, and passing an explicit matcher for the context receiver.

val serviceTwo = mock(ServiceTwo::class.java)
val serviceOne = ServiceOne(serviceTwo)

`when`(serviceTwo::call.invoke(anyObject(), eq(&quot;1&quot;))).thenReturn(&quot;1&quot;)

I had to define a helper to fix the non-null issue of Mockito, without brining in mockito-kotlin.

object MockitoHelper {
    fun &lt;T&gt; anyObject(): T {
        Mockito.any&lt;T&gt;()
        return uninitialized()
    }
    @Suppress(&quot;UNCHECKED_CAST&quot;)
    fun &lt;T&gt; uninitialized(): T = null as T
}

Perhaps MockK will add support for this in the future, but I would recommend using interfaces and stubs instead of mocking. This would heavily simplify this pattern.

huangapple
  • 本文由 发表于 2023年4月17日 23:03:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76036570.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定