如何对抛出的kotlin协同程序进行单元测试



(Kotlin 1.5.21,kotlinx协同程序测试1.5.0)

请考虑androidx.lifecycle.ViewModel:中的以下代码

fun mayThrow(){
val handler = CoroutineExceptionHandler { _, t -> throw t }
vmScope.launch(dispatchers.IO + handler) {
val foo = bar() ?: throw IllegalStateException("oops")
withContext(dispatchers.Main) {
_someLiveData.value = foo
}
}
}

vmScope对应于viewModelScope,在测试中它被TestCoroutineScope代替。dispatchers.IODispatchers.IO的代理,在测试中它是TestCoroutineDispatcher。在这种情况下,如果bar()返回null,则应用程序的行为是未定义的,所以如果是这种情况,我希望它崩溃。现在我正在尝试(JUnit4)测试这个代码:

@Test(expected = IllegalStateException::class)
fun `should crash if something goes wrong with bar`()  {
tested.mayThrow()
}

测试失败是因为它应该测试的异常:

Exception in thread "Test worker @coroutine#1" java.lang.IllegalStateException: oops
// stack trace
Expected exception: java.lang.IllegalStateException
java.lang.AssertionError: Expected exception: java.lang.IllegalStateException
// stack trace

我感觉我错过了一些显而易见的东西。。。问题:ViewModel中的代码是从协同程序抛出异常的正确方法吗?如果是,我如何对其进行单元测试?

  1. 为什么测试是绿色的:

launch{ ... }中的代码与测试方法。要识别它,请尝试修改mayThrow方法(参见代码下面的代码段),因此它会返回一个结果,而不管发生了什么在launch {...}内部将launch替换为runBlocking(更多文档中的详细信息,请阅读第一章并运行示例)


@Test
fun test() {
assertEquals(1, mayThrow()) // GREEN
}
fun mayThrow(): Int {
val handler = CoroutineExceptionHandler { _, t -> throw t }
vmScope.launch(dispatchers.IO + handler) {
val foo = bar() ?: throw IllegalStateException("oops")
withContext(dispatchers.Main) {
_someLiveData.value = foo
}
}
return 1 // this line succesfully reached
}
  1. 为什么它看起来像"由于相同的异常,测试失败">

测试没有失败,但我们在控制台中看到异常stacktrace,因为默认的异常处理程序是这样工作的,并且它是应用的,因为在这种情况下,自定义异常处理程序CoroutineExceptionHandler抛出(详细解释)

  1. 如何测试

函数mayThrow的职责太多,这就是它很难测试的原因。这是一个标准问题,有标准的处理方法(第一,第二):长话短说就是应用单一责任原则。例如,将异常处理程序传递给函数

fun mayThrow(xHandler: CoroutineExceptionHandler){
vmScope.launch(dispatchers.IO + xHandler) {
val foo = bar() ?: throw IllegalStateException("oops")
withContext(dispatchers.Main) {
_someLiveData.value = foo
}
}
}
@Test(expected = IllegalStateException::class)
fun test() {
val xRef = AtomicReference<Throwable>()
mayThrow(CoroutineExceptionHandler { _, t -> xRef.set(t) })
val expectedTimeOfAsyncLaunchMillis = 1234L
Thread.sleep(expectedTimeOfAsyncLaunchMillis)
throw xRef.get() // or assert it any other way
}

如果其他方法都不起作用,我可以建议将引发异常的代码移动到另一个方法,并测试这个方法:

// ViewModel
fun mayThrow(){
vmScope.launch(dispatchers.IO) {
val foo = doWorkThatThrows()
withContext(dispatchers.Main) {
_someLiveData.value = foo
}
}
}
fun doWorkThatThrows(): Foo {
val foo = bar() ?: throw IllegalStateException("oops")
return foo
}
// Test
@Test(expected = IllegalStateException::class)
fun `should crash if something goes wrong with bar`()  {
tested.doWorkThatThrows()
}

或者使用JUnit Jupiter允许使用assertThrows方法测试抛出异常。示例:

assertThrows<IllegalStateException> { tested.doWorkThatThrows() }

相关内容

  • 没有找到相关文章

最新更新