英文:
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<BusinessError>`的方法`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<BusinessError>)
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're testing a service `ServiceOne` that has a dependency on `ServiceTwo`.
`ServiceTwo` has a method `call()` with a context receiver of `Raise<BusinessError>`:
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<BusinessError>)
fun call(id: String): String {
return id
}
}
sealed class BusinessError {
object SomeError: BusinessError()
}
I'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("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)
}
}
}
When executing the test, you can see that `mockResult`, which is `serviceTwo.call(code)` has been correctly mocked, having a "1" value.
However, this test ends up failing because `r` in `val r = serviceTwo.call(code)` in `serviceOne.call("1")` 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's own `either {}` block.
How would you fix this so the mocked call doesn'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("1"))).thenReturn("1")
I had to define a helper to fix the non-null issue of Mockito, without brining 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论