android-studio - 由 AndroidViewModel 扩展时如何测试 ViewModel

我正在测试由 AndroidViewModel(application) 扩展的 ViewModel。

下面是我的 ViewModel

@RequiresApi(Build.VERSION_CODES.M)
@HiltViewModel
class HomeFragmentViewModel @Inject constructor(
    private val repository: MoviesRepository,
    application: Application
) : AndroidViewModel(application) {

    var movieData: MutableLiveData<List<Data>> = MutableLiveData()
    val isLoading = MutableLiveData(false)
    val errorMessage = MutableLiveData<String?>(null)

    init {
        getData()
    }

    private fun getData() {
        if (checkInternetConnection()) {
        viewModelScope.launch {
                isLoading.postValue(true)
                kotlin.runCatching {
                    repository.getMoviesData().sortedBy { it.title }
                }.onSuccess {
                    movieData.postValue(it)
                    isLoading.postValue(false)
                }.onFailure {
                    errorMessage.postValue(it.message)
                    isLoading.postValue(false)
                }
                isLoading.postValue(false)
            }
        } else {
            errorMessage.postValue("Internet not available")
        }
    }

    suspend fun filterMovies(searchTerm: String) {
        movieData.value = repository.getMoviesData().filter { mvData ->
            mvData.genre.contains(searchTerm, ignoreCase = true) ||
                    mvData.title.contains(searchTerm, ignoreCase = true)
        }
    }

    private fun checkInternetConnection(): Boolean {
        val connectivityManager =
            getApplication<Application>().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val activeNetwork = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
        return when {
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
            else -> false
        }
    }
}

现在我希望在测试中模拟这个应用程序类来初始化视图模型来编写测试用例。

下面是我写的测试:

class HomeFragmentViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @ExperimentalCoroutinesApi
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    private val repository: MoviesRepository = mockk{
        coEvery { getMoviesData() } returns movieList
    }

    private val observer: Observer<List<Data>> = mockk(relaxed = true)

    private lateinit var viewModel: HomeFragmentViewModel

    val context: Context = mock(Context::class.java)

    val application: Application = mock(Application::class.java)

    private val movieList = listOf(
        Data(
            id = 0,
            year = "1984",
            title = "Coming to America",
            genre = "Comedy",
            poster = ""
        ),
        Data(
            id = 1,
            year = "2022",
            title = "Coming 2 America",
            genre = "Comedy",
            poster = ""
        ),
        Data(
            id = 2,
            year = "2020",
            title = "Lucky",
            genre = "Action",
            poster = ""
        )
    )

    @Before
    fun setUp() {
        viewModel = HomeFragmentViewModel(repository, application = application)
        viewModel.movieData.observeForever(observer)

    }

    @After
    fun tearDown() {
        viewModel.movieData.removeObserver(observer)
    }

    @Test
    fun `filter movies by genre should return matching genres`() = runBlocking {
        val expectedList = listOf(
            Data(
                id = 0,
                year = "1984",
                title = "Coming to America",
                genre = "Comedy",
                poster = ""
            ),
            Data(
                id = 1,
                year = "2022",
                title = "Coming 2 America",
                genre = "Comedy",
                poster = ""
            )
        )
        viewModel.filterMovies("Comedy")
        verify { observer.onChanged(expectedList) }
        assertEquals(expectedList, viewModel.movieData.value)
    }
}

现在在运行测试时出现以下错误:

class java.lang.Object cannot be cast to class android.net.ConnectivityManager (java.lang.Object is in module java.base of loader 'bootstrap'; android.net.ConnectivityManager is in unnamed module of loader 'app')
java.lang.ClassCastException: class java.lang.Object cannot be cast to class android.net.ConnectivityManager (java.lang.Object is in module java.base of loader 'bootstrap'; android.net.ConnectivityManager is in unnamed module of loader 'app')
    at com.harry.moviesapp.viewModel.HomeFragmentViewModel.checkInternetConnection(HomeFragmentViewModel.kt:62)
    at com.harry.moviesapp.viewModel.HomeFragmentViewModel.getData(HomeFragmentViewModel.kt:34)
    at com.harry.moviesapp.viewModel.HomeFragmentViewModel.<init>(HomeFragmentViewModel.kt:30)
    at com.harry.moviesapp.viewModel.HomeFragmentViewModelTest.setUp(HomeFragmentViewModelTest.kt:77)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.RunBefores.invokeMethod(RunBefores.java:33)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

我检查了类似的链接,例如 https://stackoverflow.com/questions/55599337/how-to-mock-application-class-to-unit-test-viewmodel

但是我的代码不起作用。有没有办法使用 Mockito 初始化应用程序类?

回答1

首先是检查使用 mockk 而不是 mock 来生成测试双打。

但问题本身是没有为 ConnectivityManager 提供模拟:

every { 
    context.getSystemService(Context.CONNECTIVITY_SERVICE) 
} returns yourMock

以上很复杂(与上下文的mock相关的一切都很复杂)

也许您可以将互联网验证移至另一个类并生成对该实用程序的静态访问,这也不是使用 AndroidViewModel 所必需的。

相似文章

swift - 动画和数组下标 SwiftUI

我正在尝试做一个非常简单的动画拖放列表(不是经典的List(),自定义)。这意味着用户可以拖动列表中的项目。但是,当通过两个arrays和一个计算索引访问动画时,我似乎遇到了问题(请参阅问题末尾的Dr...

最新文章