所以我对如何实现ViewModel的单元测试感到困惑,我正在使用reflect来获取和使用存储库的API。
ViewModel.kt
@HiltViewModel
class MoviesViewModel @Inject constructor(private val moviesRepository: MoviesRepository) :
ViewModel() {
private val _navigatetoDetail = MutableLiveData<Movies?>()
fun getPopularMovies() = liveData(Dispatchers.Default) {
emit(Resource.loading(null))
try {
emit(Resource.success(moviesRepository.getPopularMovies()))
} catch (e: Exception) {
emit(
Resource.error(
null,
e.message ?: "Unknown Error"
)
)
Log.e("viewModel", "popularMovies error: ${e.message}")
}
}
fun getMovieDetails(movie_id: String) = liveData(Dispatchers.Default) {
emit(Resource.loading(null))
try {
emit(Resource.success(moviesRepository.getMovieDetails(movie_id)))
} catch (e: Exception) {
emit(
Resource.error(
null,
e.message ?: "Unknown Error"
)
)
Log.e("viewModel", "movieDetails error: ${e.message}")
}
}
fun navigatetoDetail(): LiveData<Movies?> {
return _navigatetoDetail
}
fun onMovieClicked(movies: Movies?) {
_navigatetoDetail.value = movies
}
fun onMovieDetailNavigated() {
_navigatetoDetail.value = null
}
字符串
Repository.kt
class MoviesRepository @Inject constructor(private var apiInterface: ApiInterface) {
init {
apiInterface = ApiBuilder.createService()
}
suspend fun getPopularMovies() = apiInterface.getPopularMovies()
suspend fun getMovieDetails(movie_id: String) = apiInterface.getMovieDetails(movie_id)
suspend fun getPopularTvShows() = apiInterface.getPopularTvShows()
suspend fun getTvShowDetails(tvshow_id: String) = apiInterface.getTvShowDetails(tvshow_id)
型
apiInterface.kt
interface ApiInterface {
@GET("/3/movie/popular?api_key=$API_KEY&language=en-US")
suspend fun getPopularMovies(): Envelope<List<Movies>>
@GET("/3/movie/{movie_id}?api_key=$API_KEY&language=en-US")
suspend fun getMovieDetails(@Path("movie_id") movie_id: String?): Movies
@GET("3/tv/popular?api_key=$API_KEY&language=en-US&page=1")
suspend fun getPopularTvShows(): Envelope<List<TvShows>>
@GET("/3/tv/{tvshow_id}?api_key=$API_KEY&language=en-US")
suspend fun getTvShowDetails(@Path("tvshow_id") tvshow_id: String?): TvShows
型
我已经试着通过这样做来测试我的ViewModel:
@Test
fun testGetPopularMovies() = coroutinesTestRule.testDispatcher.runBlockingTest {
val moviesList = viewModel.getPopularMovies().value
viewModel.getPopularMovies().observeForever(observer)
verify(observer).onChanged(argumentCaptor.capture())
assertEquals(20, moviesList?.data?.results?.size)
}
型
但它在 viewModel.getPopularMovies().ObserveForever(observer) 上返回NullPointerException
3条答案
按热度按时间rryofs0p1#
这里是一个用于等待实时数据结果的扩展函数。使用下面的代码在测试包下创建koltin扩展函数。
字符串
这里是视图模型类测试示例。LoginViewmodel类扩展AndroidViewModel而不是ViewModel。
型
nkoocmlb2#
你需要有一个testCoroutineDispatcher或testCoroutineScope来设置你的viewModel的范围到测试的范围。添加这个类:
字符串
并在测试类中使用:
型
不要忘记使用runBlockingTest来测试函数。runBlockingTest在coroutinesTest库中可用:
testImplementation(“org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3”)
型
还使用getOrWaitValue观察API结果
wf82jlnq3#
确保你在单元测试/模拟中是来自流的
emitting
值。如果你不发出值,那么你在ViewModel
中的LiveData
将永远不会被设置任何值,因为没有什么可收集的。你在LiveData
上设置值的视图模型中的collect
内的代码将不会被执行。另一种选择是使用
TestDispatcher
如果你使用的是StandardTestDispatcher
,那么你需要从调用advanceUntilIdle()
函数来执行在测试中调度的协程TestCoroutine。如果你使用的是
UnconfinedTestDispatcher
,那么你可以删除advanceUntilIdle()
,因为它开始急切地执行协程。协同程序测试库
字符串
方法1
型
方法2
型
方法3
通过使用android架构组件中提供的getOrAwaitValue()示例
https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/test/java/com/android/example/livedatabuilder/util/LiveDataTestUtil.kt
型