You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程场景下JUnit4 @Test(expected)测试预期异常失败问题

问题:JUnit4 @Test(expected)无法检测Kotlin协程中的异常

我参考StackOverflow文章《Test expected exceptions in Kotlin》,尝试用JUnit4的@Test(expected = Exception::class)注解检测Kotlin协程场景下的异常,但测试执行失败,报错信息如下:

Expected exception: java.lang.Exception
java.lang.AssertionError: Expected exception: java.lang.Exception

相关代码

测试代码

@Test(expected = Exception::class)
fun `repo fails fetching top rated movies`() = runTest {
    val remoteDataSrc = mockk<MoviesRemoteDataSource>()
    val dispatcher = UnconfinedTestDispatcher(testScheduler)
    val repository = MoviesRepository(remoteDataSrc, dispatcher)
    repository.movies = emptyList()

    coEvery { remoteDataSrc.getTopRatedMovies() } throws Exception()

    repository.getTopRatedMovies()
}

MoviesRepository代码

class MoviesRepository(
private val remoteDataSource: MoviesRemoteDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
var movies: List<Movie> = emptyList()

suspend fun getTopRatedMovies(): List<Movie> {
    if (movies.isNotEmpty()) return movies
    withContext(ioDispatcher) {
        try {
            movies = remoteDataSource.getTopRatedMovies().map { it.toDomain() }
        } catch (ex: Exception) {
        }
    }
    return movies
  }
}

问题原因与修复方案

核心原因

  1. 异常被Repository内部吞噬:在getTopRatedMovies()的withContext代码块中,你用try-catch捕获了Exception但没有任何处理(空的catch块),异常根本没有向上传递到测试代码层面,JUnit自然无法检测到预期异常。
  2. JUnit4 @Test(expected)不适配协程场景:@Test(expected)是基于普通线程的异常检测机制,而协程的异常调度和处理逻辑与普通线程不同,即使异常被抛出,这种方式也可能无法正确捕获。

修复步骤

1. 修正Repository的异常处理逻辑

要么移除不必要的try-catch让异常自然向上抛出,要么在捕获后重新抛出异常:

suspend fun getTopRatedMovies(): List<Movie> {
    if (movies.isNotEmpty()) return movies
    return withContext(ioDispatcher) {
        val remoteMovies = remoteDataSource.getTopRatedMovies().map { it.toDomain() }
        movies = remoteMovies
        remoteMovies
        // 或者保留catch但重新抛出
        // try {
        //     val remoteMovies = remoteDataSource.getTopRatedMovies().map { it.toDomain() }
        //     movies = remoteMovies
        //     remoteMovies
        // } catch (ex: Exception) {
        //     throw ex
        // }
    }
}

2. 使用协程友好的异常断言方式

放弃JUnit4的@Test(expected),改用Kotlin官方推荐的kotlin.test.assertFailsWith或JUnit的assertThrows来检测协程中的异常:

import kotlin.test.assertFailsWith

@Test
fun `repo fails fetching top rated movies`() = runTest {
    val remoteDataSrc = mockk<MoviesRemoteDataSource>()
    val dispatcher = UnconfinedTestDispatcher(testScheduler)
    val repository = MoviesRepository(remoteDataSrc, dispatcher)
    repository.movies = emptyList()

    coEvery { remoteDataSrc.getTopRatedMovies() } throws Exception()

    // 断言调用方法时会抛出指定类型的异常
    assertFailsWith<Exception> {
        repository.getTopRatedMovies()
    }
}

内容的提问来源于stack exchange,提问作者HukeLau_DABA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:30:47