我有一个 ViewModel,它具有一些从服务器或本地数据库获取数据的功能。
我已经使用假存储库为该 ViewModel 编写了单元测试。
但是无论是否调用存储库的函数,我的单元测试都通过了。
我尝试了一些解决方案,但没有一个有效。
我更改了我的 ViewModel 和我的假存储库。
但什么都没有改变。
这是我的视图模型:
@HiltViewModel
class AllGamesViewModel @Inject constructor(
private val getGame: GetAllGamesUseCase,
private val getGamesByGenre: GetGamesByGenreUseCase,
private val getAllGamesFromLocalUseCase: GetAllGamesFromLocalUseCase,
private val connectivityObserver: ConnectivityObserver
) : ViewModel() {
private val _state = mutableStateOf(AllGamesState())
val state: State<AllGamesState> = _state
private val _eventFlow = MutableSharedFlow<UIEvent>()
val eventFlow = _eventFlow.asSharedFlow()
private val _status = MutableStateFlow(Status.Unavailable)
val status: StateFlow<Status> get() = _status
init {
networkConnectionCheck()
}
private fun networkConnectionCheck() {
viewModelScope.launch {
connectivityObserver.observe().collect{ newStatus ->
_status.value = newStatus
if(_status.value == Status.Available){
getAllGames()
}else{
getAllGamesFromLocal()
}
}
}
}
fun getAllGames() {
viewModelScope.launch {
getGame().collect { result ->
withContext(Dispatchers.Main){
handleResult(result)
}
}
}
}
fun getAllGamesFromLocal(){
viewModelScope.launch {
getAllGamesFromLocalUseCase().collect{ result ->
withContext(Dispatchers.Main){
handleResult(result)
}
}
}
}
fun getTaggedGames(genre: String) {
viewModelScope.launch {
getGamesByGenre(genre).collect { result ->
withContext(Dispatchers.Main){
handleResult(result)
}
}
}
}
private fun handleResult(result:Resource<List<Game>>){
when (result) {
is Resource.Success ->{
_state.value = _state.value.copy(
allGames = result.data ?: emptyList(),
isLoading = false
)
}
is Resource.Error ->{
_state.value = _state.value.copy(
allGames = result.data ?: emptyList(),
isLoading = false,
)
viewModelScope.launch {
_eventFlow.emit(
UIEvent.ShowSnackBar(
result.message!!
)
)
}
}
is Resource.Loading ->{
_state.value = _state.value.copy(
allGames = result.data ?: emptyList(),
isLoading = true,
)
}
}
}
}
我使用 Fake Repository 来测试 ViewModel,如下所示:
class FakeGameRepository:GameRepository{
private var games = mutableListOf<Game>()
override fun getAllGames(): Flow<Resource<List<Game>>> {
return flow { emit(Resource.Success(games)) }
}
override fun getGameById(id: Int): Flow<Resource<Game>> {
val game = games.find { it.id == id }
return flow { emit(Resource.Success(game)) }
}
override fun getGameByGenre(genre: String): Flow<Resource<List<Game>>> {
val taggedGames = mutableListOf<Game>()
games.forEach { game ->
if(game.genre == genre){
taggedGames.add(game)
}
}
return flow { emit(Resource.Success(taggedGames)) }
}
override fun getAllGamesFromLocal(): Flow<Resource<List<Game>>> {
return flow { emit(Resource.Success(games)) }
}
fun insertGame(insertedGames:List<Game>){
games.addAll(insertedGames)
}
}
这是 ViewModel 测试:
@ExperimentalCoroutinesApi
class AllGamesViewModelTest{
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var connectivityObserver: FakeNetworkConnectivityObserver
private lateinit var viewModel: AllGamesViewModel
private lateinit var getAllGamesUseCase: GetAllGamesUseCase
private lateinit var getAllGamesFromLocalUseCase: GetAllGamesFromLocalUseCase
private lateinit var getGamesByGenreUseCase: GetGamesByGenreUseCase
private lateinit var repository: FakeGameRepository
private lateinit var game: Game
val games = mutableListOf<Game>()
@Before
fun setup(){
repository = FakeGameRepository()
connectivityObserver = FakeNetworkConnectivityObserver()
getAllGamesUseCase = GetAllGamesUseCase(repository)
getAllGamesFromLocalUseCase = GetAllGamesFromLocalUseCase(repository)
getGamesByGenreUseCase = GetGamesByGenreUseCase(repository)
viewModel = AllGamesViewModel(
getAllGamesUseCase,
getGamesByGenreUseCase,
getAllGamesFromLocalUseCase,
connectivityObserver
)
for(i in 0..10){
game = Game(
developer = "developer $i",
freeToGameProfileUrl = "freeUrl $i",
gameUrl = "gameUrl $i",
genre = "genre $i",
id = i,
platform = "plat $i",
publisher = "publish $i",
releaseDate = "release $i",
shortDescription = "short $i",
thumbnail = "thumb $i",
title = "title $i"
)
games.add(game)
}
repository.insertGame(games)
}
@Test
fun `Test getAllGames when Network is Available`() = runTest {
connectivityObserver.setStatus(Status.Available)
repository.getAllGames()
delay(500L)
assertThat(viewModel.status.value).isEqualTo(Status.Available)
assertThat(viewModel.state.value.allGames).containsExactlyElementsIn(games)
}
@Test
fun `Test getAllGamesFromLocal when Network is Unavailable`() = runTest {
connectivityObserver.setStatus(Status.Unavailable)
repository.getAllGamesFromLocal()
delay(500L)
assertThat(viewModel.status.value).isEqualTo(Status.Unavailable)
assertThat(viewModel.state.value.allGames).containsExactlyElementsIn(games)
}
}
原因是FakeRepository。
因为我没有检查网络状态,所以FakeRepository中的函数将数据传递给ViewModel。
所以我像这样更改 FakeRepository:
class FakeGameRepository @Inject constructor(
private val connectivityObserver: FakeNetworkConnectivityObserver
):GameRepository{
private var games = mutableListOf<Game>()
override fun getAllGames(): Flow<Resource<List<Game>>> {
return flow {
connectivityObserver.observe().collect{status ->
when(status){
Status.Available ->{
emit(Resource.Success(games))
}
else ->{
games = emptyList<Game>().toMutableList()
emit(Resource.Error("No Internet Connection"))
}
}
}
}
}
//...
fun insertGame(insertedGames:List<Game>){
games.addAll(insertedGames)
}
}
因此,在其类型为 FakeRepository 的变量的测试类中,我像这样传递了connectivityObserver:
repository = FakeGameRepository(connectivityObserver)