Mockito无法Mock isOnline方法,测试触发真实网络校验报错求助
问题原因
你用Mockito mock isOnline 方法无效,核心原因是该方法是目标类(sut)的非open成员方法。Mockito默认基于动态代理实现mock,只能处理接口或标记为open的类/方法;普通final方法(Kotlin默认方法为final)会绕过mock逻辑,直接执行真实的网络校验代码,导致测试报错。
解决方案
方案一:重构代码,抽离网络校验依赖(推荐)
将网络判断逻辑从ViewModel中剥离为独立工具类,通过依赖注入引入,这样可以完全隔离真实网络逻辑,测试时直接mock这个工具类即可。
1. 新建网络工具类
class NetworkConnectivityHelper(private val context: Context) { fun isOnline(): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) if (capabilities != null) { if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { return true } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { return true } } return false } }
2. 修改ViewModel,注入工具类
class YourViewModel( private val networkHelper: NetworkConnectivityHelper, private val getCharactersFromDataBaseUseCase: GetCharactersFromDataBaseUseCase, private val getAllCharactersUseCase: GetAllCharactersUseCase ) : ViewModel() { private val _getAllCharactersResponseLiveData = MutableLiveData<Resource<List<Character>>>() val getAllCharactersLiveData: LiveData<Resource<List<Character>>> = _getAllCharactersResponseLiveData fun getAllCharacters() = viewModelScope.launch { _getAllCharactersResponseLiveData.postValue(Resource.Loading()) val charactersList = getCharactersFromDataBaseUseCase.getAllCharactersFromDataBase() if (networkHelper.isOnline() && charactersList.isEmpty()) { _getAllCharactersResponseLiveData.postValue(getAllCharactersUseCase.getAllCharacters()) } else if (!networkHelper.isOnline() && charactersList.isNotEmpty() || networkHelper.isOnline() && charactersList.isNotEmpty() ) { _getAllCharactersResponseLiveData.postValue( Resource.Success( getCharactersFromDataBaseUseCase.getAllCharactersFromDataBase() ) ) } else { _getAllCharactersResponseLiveData.postValue(Resource.Error()) } } }
3. 测试代码中mock工具类
@Test fun internet_connected_and_empty_data_base_gets_info_from_api_success() { // given val mockNetworkHelper = mock(NetworkConnectivityHelper::class.java) `when`(mockNetworkHelper.isOnline()).thenReturn(true) // mock其他依赖的返回值 `when`(getCharactersFromDataBaseUseCase.getAllCharactersFromDataBase()).thenReturn(emptyList()) `when`(getAllCharactersUseCase.getAllCharacters()).thenReturn(Resource.Success(listOf(Character(name = "char1")))) val sut = YourViewModel(mockNetworkHelper, getCharactersFromDataBaseUseCase, getAllCharactersUseCase) // when sut.getAllCharacters() val expected = sut.getAllCharactersLiveData.getOrAwaitValue().data // then assertThat(expected?.first()?.name).isEqualTo("char1") }
方案二:用Mockito Spy做部分mock(临时 workaround)
如果不想重构代码,可以用Spy创建目标类的部分mock实例,但需要先把isOnline方法改为open(因为Kotlin默认方法是final,Spy无法代理)。
1. 修改ViewModel的isOnline方法为open
open fun isOnline(context: Context): Boolean { // 原逻辑保持不变 }
2. 测试代码使用Spy
@Test fun internet_connected_and_empty_data_base_gets_info_from_api_success() { // given val sut = spy(YourViewModel(...)) // 传入构造所需的其他依赖 `when`(sut.isOnline(context)).thenReturn(true) // mock其他依赖的返回值 `when`(getCharactersFromDataBaseUseCase.getAllCharactersFromDataBase()).thenReturn(emptyList()) `when`(getAllCharactersUseCase.getAllCharacters()).thenReturn(Resource.Success(listOf(Character(name = "char1")))) // when sut.getAllCharacters() val expected = sut.getAllCharactersLiveData.getOrAwaitValue().data // then assertThat(expected?.first()?.name).isEqualTo("char1") }
方案对比
- 方案一符合SOLID原则,降低ViewModel职责,后续测试、维护更灵活,是长期最优解。
- 方案二属于临时修复,需要修改方法的访问修饰符,破坏封装性,仅适合快速验证场景。
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

