单元测试挂起协程

时间:2021-05-13 19:37:54

标签: unit-testing kotlin kotlin-coroutines

对 Kotlin 有点新并对其进行测试...我正在尝试使用挂起方法来测试 dao 对象包装器,该方法使用 awaitFirst() 作为 SQL 返回对象。然而,当我为它编写单元测试时,它只是陷入了一个循环。我认为这是因为 awaitFirst() 不在测试的同一范围内

实施:

suspend fun queryExecution(querySpec: DatabaseClient.GenericExecuteSpec): OrderDomain {
        var result: Map<String, Any>?
        try {
            result = querySpec.fetch().first().awaitFirst()
        } catch (e: Exception) {
            if (e is DataAccessResourceFailureException)
                throw CommunicationException(
                    "Cannot connect to " + DatabaseConstants.DB_NAME +
                        DatabaseConstants.ORDERS_TABLE + " when executing querySelect",
                    "querySelect",
                    e
                )
            throw InternalException("Encountered R2dbcException when executing SQL querySelect", e)
        }

        if (result == null)
            throw ResourceNotFoundException("Resource not found in Aurora DB")

        try {
            return OrderDomain(result)
        } catch (e: Exception) {
            throw InternalException("Exception when parsing to OrderDomain entity", e)
        } finally {
            logger.info("querySelect;stage=end")
        }
    }

单元测试:

@Test
    fun `get by orderid id, null`() = runBlocking {
        // Assign
        Mockito.`when`(fetchSpecMock.first()).thenReturn(monoMapMock)
        Mockito.`when`(monoMapMock.awaitFirst()).thenReturn(null)

        // Act & Assert
        val exception = assertThrows<ResourceNotFoundException> {
            auroraClientWrapper.queryExecution(
                databaseClient.sql("SELECT * FROM orderTable WHERE orderId=:1").bind("1", "123") orderId
            )
        }
        assertEquals("Resource not found in Aurora DB", exception.message)
    }

我在 https://github.com/Kotlin/kotlinx.coroutines/issues/1204 上注意到了这个问题,但没有任何解决方法对我有用...

在单元测试中使用 runBlocking 只会导致我的测试永远无法完成。使用 runBlockingTest 会显式地抛出一个错误,指出“作业从未完成”……有人知道吗?此时有任何黑客攻击吗?

此外,我相当理解您不应该将挂起与块一起使用,因为这有点违背了挂起的目的,因为它释放线程以稍后继续,而阻塞会强制线程等待结果......但是那么这是如何工作的?

private suspend fun queryExecution(querySpec: DatabaseClient.GenericExecuteSpec): Map {
        var result: Map<String, Any>?
        try {
            result = withContext(Dispatchers.Default) {
                querySpec.fetch().first().block()
            }
return result
}

这是否意味着 withContext 将使用一个新线程,并在其他地方重新使用旧线程?那么这并没有真正优化任何东西,因为无论产生一个新的上下文,我仍然会有一个被阻塞的线程?

1 个答案:

答案 0 :(得分:0)

找到解决方案。

monoMapMock 是来自 Mockito 的模拟值。似乎 kotlinx-test 协程无法拦截异步以返回单声道。所以我强制我可以模拟的方法返回一个真正的 Mono 值而不是一个 Mocked Mono。这样做,正如路易斯所建议的那样。我不再嘲笑它并返回一个真正的价值

@Test
    fun `get by orderid id, null`() = runBlocking {
        // Assign
        Mockito.`when`(fetchSpecMock.first()).thenReturn(Mono.empty())
        Mockito.`when`(monoMapMock.awaitFirst()).thenReturn(null)

        // Act & Assert
        val exception = assertThrows<ResourceNotFoundException> {
            auroraClientWrapper.queryExecution(
                databaseClient.sql("SELECT * FROM orderTable WHERE orderId=:1").bind("1", "123") orderId
            )
        }
        assertEquals("Resource not found in Aurora DB", exception.message)
    }