英文:
Asserting a coroutine is not complete under unit test
问题
I have a suspendible function that I want to assert does NOT complete with a result under certain conditions. I have tried to write the following extension, which aims to wait 5 seconds before asserting whether the Job is complete (I deem this check sufficient for knowing that the suspendible is still hanging):
第一种方法:
// Extension Function
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
val result = async { block }
advanceTimeBy(5000L)
val isBlockComplete = result.isCompleted
assertThat(isBlockComplete, equalTo(false))
}
// Usage
@Test
fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() =
runTest {
assertNotCompleted {
someClass.waitForValue()
}
}
在这种情况下,然而,result.isCompleted
总是返回 true,即使它应该是 false。如果我删除 advanceTimeBy(5000L)
,那么 result.isCompleted
总是返回 false
,即使我修改测试以实际返回某些内容。
我尝试了另一种方法,通过使用 getCompletionExceptionOrNull()
抛出 IllegalStateException
。这确实可以工作,但它会导致一个奇怪的接口,我们需要使用一个 'expected' 属性来注释使用它的每个测试。如果可能的话,我想避免这种情况,因为测试可能在其他地方抛出异常,从而导致测试可能错误地通过。
第二种方法:
// Extension Function
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
async(block = block).run {
this@assertNotCompleted.advanceTimeBy(5000L)
getCompletionExceptionOrNull()
}
}
// Usage - wanting to avoid the need for expected property
@Test(expected = IllegalStateException::class)
fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() =
runTest {
assertNotCompleted {
someClass.waitForValue()
}
}
我尝试捕获此异常,但测试无论何时都会通过或失败,这取决于 advanceTimeBy(5000L)
的位置 - 无论挂起是否完成。
第三种方法:
// Extension Function
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
runCatching {
val result = async {
block
// advanceTimeBy(5000L) 测试总是通过
}
// advanceTimeBy(5000L) 测试总是失败
result.getCompletionExceptionOrNull()
}.also {
assertThat(it.isFailure, equalTo(true))
}
}
// Usage 与第一种方法相同
提前感谢您。
英文:
I have a suspendible function that I want to assert does NOT complete with a result under certain conditions. I have tried to write the following extension, which aims to wait 5 seconds before asserting whether the Job is complete (I deem this check sufficient for knowing that the suspendible is still hanging):
First Approach:
// Extension Function
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
val result = async { block }
advanceTimeBy(5000L)
val isBlockComplete = result.isCompleted
assertThat(isBlockComplete, equalTo(false))
}
// Usage
@Test
fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() =
runTest {
assertNotCompleted {
someClass.waitForValue()
}
}
In this scenario, however, result.isCompleted
is always returning true whenever it should be false. And if I remove the advanceTimeBy(5000L)
, then result.isCompleted
always returns false
even if I modify the test to actually return something.
I have tried another approach, which throws an IllegalStateException
by using getCompletionExceptionOrNull()
. This does actually work, but it results in a strange interface whereby we need to annotate every test that uses it with an 'expected' property. I would like to avoid this if possible as it is possible that an exception is thrown elsewhere in the test and thus the test might pass incorrectly.
Second Approach:
// Extension Function
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
async(block = block).run {
this@assertNotCompleted.advanceTimeBy(5000L)
getCompletionExceptionOrNull()
}
}
// Usage - wanting to avoid the need for expected property
@Test(expected = IllegalStateException::class)
fun `Given no value is ready, when waitForValue is called, then the suspendable function is not complete`() =
runTest {
assertNotCompleted {
someClass.waitForValue()
}
}
I did try to catch this exception, however the tests either always pass or always fail, depending on the location of advanceTimeBy(5000L)
- irrelevant if the suspendible can complete or not.
Thrid Approach:
// Extension Function
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> TestScope.assertNotCompleted(block: suspend CoroutineScope.() -> T) {
runCatching {
val result = async {
block
// advanceTimeBy(5000L) Test ALWAYS passes
}
// advanceTimeBy(5000L) Test ALWAYS fails
result.getCompletionExceptionOrNull()
}.also {
assertThat(it.isFailure, equalTo(true))
}
}
// Usage is same as first approach
Thanks in advance.
答案1
得分: 2
Here's the translated code portion without the code itself:
给定一个总是完成的函数和一个永远不会完成的函数。
这些测试通过:
要验证这些测试是否断言了挂起函数是否已经完成,您可以交换函数,这两个测试都会失败。
英文:
Given a function that alwaysCompletes and a function that neverCompletes
suspend fun alwaysCompletes(): Int {
delay(100)
return 42
}
suspend fun neverCompletes() {
while(true) {
delay(1000)
}
}
These tests pass:
@Test
fun testAlwaysCompletes() {
runBlocking {
val job = launch {
someClass.alwaysCompletes()
}
delay(5000) // wait for 5 seconds
assertThat(true, equalTo(job.isCompleted ))
}
}
@Test
fun testNeverCompletes() {
runBlocking {
val job = launch {
someClass.neverCompletes()
}
delay(5000) // wait for 5 seconds
assertThat(false, equalTo(job.isCompleted ))
job.cancelAndJoin() // cancel the job to avoid leaking resources
}
}
or simulating the time with advanceTimeBy:
@Test
fun testAlwaysCompletes() = runTest {
val job = launch {
someClass.alwaysCompletes()
}
advanceTimeBy(10_000) // advance time by 10,000ms
assertFalse(job.isActive) // job should not be active
assertTrue(job.isCompleted) // job should now be completed
}
@Test
fun testNeverCompletes() = runTest {
val job = launch {
someClass.neverCompletes()
}
advanceTimeBy(10_000) // advance time by 10,000ms
assertTrue(job.isActive) // job should still be active
job.cancelAndJoin() // cancel the job to avoid leaking resources
assertFalse(job.isActive) // job should now be cancelled and not active
}
To verify these tests assert that the suspend function has or hasn't completed, you can switch the functions over and both tests will fail
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论