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.