【问题标题】:Problems with Kotlin Result<T> on unit testsKotlin Result<T> 在单元测试中的问题
【发布时间】:2020-12-23 07:54:30
【问题描述】:

我正在开发一个 android 应用程序,我选择使用 Kotlin Result 类来处理我的操作的成功/失败。我对代码进行了更改,但测试停止工作,我不明白为什么。这里我给你看一些sn-ps:

FireStoreClient.kt

suspend fun items(): Result<ItemsResponse>

NetworkDataSource.kt

suspend fun getItems(): List<Item> =
    fireStoreClient.items().fold({ it.items.map { item -> item.toDomain() } }, { emptyList() })

NetworkDataSourceTest.kt

@ExperimentalCoroutinesApi
@Test
fun `Check getItems works properly`() = runBlockingTest {
    whenever(fireStoreClient.items()).doReturn(success(MOCK_ITEMS_DOCUMENT))
    val expectedResult = listOf(
        Item(
            id = 1,
            desc = "Description 1"
        ),
        Item(
            id = 2,
            desc = "Description 2"
        )
    )
    assertEquals(expectedResult, dataSource.getItems())
}

这是我现在遇到的例外。有什么线索吗?单元测试时似乎没有执行 fold() 方法。

java.lang.ClassCastException: kotlin.Result cannot be cast to ItemsResponse

    at NetworkDataSource.getItems(NetworkDataSource.kt:31)

【问题讨论】:

标签: android unit-testing kotlin


【解决方案1】:

对于那些不想创建自己的Result 类型的人来说,我已经为这个结果包装问题找到了一个不同的解决方法。

当在 suspend 函数上使用 Mockito 的 .thenReturn 时,似乎会特别出现此问题。我发现使用.thenAnswer 不会出现问题。

所以不要在你的单元测试中写这个(在这里将doReturn更改为thenReturn):

whenever(fireStoreClient.items()).thenReturn(success(MOCK_ITEMS_DOCUMENT))

用途:

whenever(fireStoreClient.items()).thenAnswer { success(MOCK_ITEMS_DOCUMENT) }

编辑:我应该注意到在运行 Kotlin 1.5.0 时我仍然遇到这个问题。

编辑:在 Kotlin 1.5.20 上,我可以再次使用 .thenReturn

【讨论】:

    【解决方案2】:

    在深入研究了这个问题之后,我终于找到了一个在测试环境中有效的临时解决方法。问题是,不知何故,Result 对象的值被另一个 Result 包裹,我们可以使用反射提取所需的值或异常。

    所以,我创建了一个名为 mockSafeFold 的扩展函数,它实现了正常调用中的 fold 行为,并且在您执行单元测试时表现良好。

    inline fun <R, reified T> Result<T>.mockSafeFold(
        onSuccess: (value: T) -> R,
        onFailure: (exception: Throwable) -> R
    ): R = when {
        isSuccess -> {
            val value = getOrNull()
            try {
                onSuccess(value as T)
            } catch (e: ClassCastException) {
                // This block of code is only executed in testing environment, when we are mocking a
                // function that returns a `Result` object.
                val valueNotNull = value!!
                if ((value as Result<*>).isSuccess) {
                    valueNotNull::class.java.getDeclaredField("value").let {
                        it.isAccessible = true
                        it.get(value) as T
                    }.let(onSuccess)
                } else {
                    valueNotNull::class.java.getDeclaredField("value").let {
                        it.isAccessible = true
                        it.get(value)
                    }.let { failure ->
                        failure!!::class.java.getDeclaredField("exception").let {
                            it.isAccessible = true
                            it.get(failure) as Exception
                        }
                    }.let(onFailure)
                }
            }
        }
        else -> onFailure(exceptionOrNull() ?: Exception())
    }
    

    然后,只需调用它而不是 fold

    val result: Result = myUseCase(param)
    
    result.mockSafeFold(
        onSuccess = { /* do whatever */ },
        onFailure = { /* do whatever */ }
    )
    

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题。

      我注意到我应该返回Result&lt;List&lt;Any&gt;&gt; 的注入类方法实际上返回了Result&lt;Result&lt;List&lt;Any&gt;&gt;&gt;,这导致了ClassCastException。我对方法的结果使用了Evaluate Expression 选项,我得到了

      Success(Success([]))
      

      应用运行良好,但由于此问题,单元测试未通过。

      作为一个临时解决方案,我使用fold() 扩展函数构建了Result 密封类的新简单实现。以后应该很容易替换成kotlin.Result

      Result密封类:

      sealed class Result<T> {
          data class Success<T>(val value: T) : Result<T>()
          data class Failure<T>(val error: Throwable) : Result<T>()
      }
      

      fold()扩展功能:

      inline fun <R, T> Result<T>.fold(
          onSuccess: (value: T) -> R,
          onFailure: (exception: Throwable) -> R
      ): R = when (this) {
          is Result.Success -> onSuccess(value)
          is Result.Failure -> onFailure(error)
      }
      

      【讨论】:

      • 你说得对 Patryk,这就是我所经历的。谢谢你的帮助。我在 Kotlin 项目中发现了 this issue。 1.4.30解决。
      猜你喜欢
      • 1970-01-01
      • 2020-07-14
      • 1970-01-01
      • 2020-09-02
      • 1970-01-01
      • 2012-12-01
      • 2011-05-01
      • 2012-07-09
      • 2011-10-28
      相关资源
      最近更新 更多