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 } }
问题原因与修复方案
核心原因
- 异常被Repository内部吞噬:在
getTopRatedMovies()的withContext代码块中,你用try-catch捕获了Exception但没有任何处理(空的catch块),异常根本没有向上传递到测试代码层面,JUnit自然无法检测到预期异常。 - 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
相关产品推荐
相关产品推荐

