简体   繁体   English

使用存储库模式和 LiveData 对 ViewModel 进行单元测试

[英]Unit test ViewModel using repository pattern and LiveData

I want to write a unit test for my viewmodel class:我想为我的视图模型 class 编写一个单元测试:

class MainViewModel(
    repository: ShowRepository
) : ViewModel() {

    private val _shows = repository.shows
    val shows: LiveData<MyResult<List<Show>>>
        get() = _shows
}

Here is my repository class:这是我的存储库 class:

class ShowRepository(
    private val dao: ShowDao,
    private val api: TVMazeService,
    private val context: Context
) {

    /**
     * A list of shows that can be shown on the screen.
     */
    val shows = resultLiveData(
        databaseQuery = {
            Transformations.map(dao.getShows()) {
                it.asDomainModel()
            }
        },
        networkCall = { refreshShows() })

    /**
     * Refresh the shows stored in the offline cache.
     */
    suspend fun refreshShows(): MyResult<List<Show>> =
        try {
            if (isNetworkAvailable(context)) {
                val shows = api.fetchShowList().await()
                dao.insertAll(*shows.asDatabaseModel())
                MyResult.success(shows)
            } else {
                MyResult.error(context.getString(R.string.failed_internet_msg))
            }
        } catch (err: HttpException) {
            MyResult.error(context.getString(R.string.failed_loading_msg))
        } catch (err: UnknownHostException) {
            MyResult.error(context.getString(R.string.failed_unknown_host_msg))
        } catch (err: SocketTimeoutException) {
            MyResult.error(context.getString(R.string.failed_socket_timeout_msg))
        }
}

And here is my Dao class:这是我的刀 class:

@Dao
interface ShowDao {

    /**
     * Select all shows from the shows table.
     *
     * @return all shows.
     */
    @Query("SELECT * FROM databaseshow")
    fun getShows(): LiveData<List<DatabaseShow>>
}

Here is my unit test:这是我的单元测试:

@ExperimentalCoroutinesApi
class MainViewModelTest {

    private lateinit var viewModel: MainViewModel
    private lateinit var repository: ShowRepository
    private val api: TVMazeService = mock()
    private val dao: ShowDao = mock()
    private val context: Context = mock()

    @Test
    fun fetch() {
        val observer1: Observer<List<DatabaseShow>> = mock()
        dao.getShows().observeForever(observer1)

        repository = ShowRepository(dao, api, context)

        val observer2: Observer<MyResult<List<Show>>> = mock()
        repository.shows.observeForever(observer2)

        viewModel = MainViewModel(repository)

        val observer3: Observer<MyResult<List<Show>>> = mock()
        viewModel.shows.observeForever(observer3)

        verify(viewModel).shows
    }
}

But I receive following exception:但我收到以下异常:

java.lang.NullPointerException
    at com.android.sample.tvmaze.viewmodel.MainViewModelTest.fetch(MainViewModelTest.kt:39)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

I appreciate if you give me any guideline.如果您给我任何指导,我将不胜感激。

I change my Dao method to return Flow instead of LiveData:我将我的 Dao 方法更改为返回 Flow 而不是 LiveData:

@Dao
interface ShowDao {
   /**
   * Select all shows from the shows table.
   *
   * @return all shows.
   */
   @Query("SELECT * FROM databaseshow")
   fun getShows(): Flow<List<DatabaseShow>>
}

And I could successfully run my tests such as:我可以成功运行我的测试,例如:

    @Test
    fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
        mockkStatic("com.android.sample.tvmaze.util.ContextExtKt")
        every {
            context.isNetworkAvailable()
        } returns true
        `when`(api.fetchShowList()).thenReturn(Calls.response(Response.success(emptyList())))
        `when`(dao.getShows()).thenReturn(flowOf(emptyList()))
        val repository = ShowRepository(dao, api, context, TestContextProvider())
        val viewModel = MainViewModel(repository).apply {
            shows.observeForever(resource)
        }
        try {
            verify(resource).onChanged(Resource.loading())
            verify(resource).onChanged(Resource.success(emptyList()))
        } finally {
            viewModel.shows.removeObserver(resource)
        }
    }

I would mock repository and instruct mockito true Mockito.when().doReturn() to return some data, and verify that the LiveData output is correct.我将模拟存储库并指示 mockito true Mockito.when().doReturn()返回一些数据,并验证LiveData output 是否正确。

Of course you could use an instance ShowRepository .当然,您可以使用实例ShowRepository You will still need to instruct mockito on how to return when the execution hits the mocked object.您仍然需要指示 mockito 在执行到模拟的 object 时如何返回。 As before you can change the behaviour w和以前一样,您可以更改行为 w

This line is wrong verify(viewModel).shows .这一行是错误的verify(viewModel).shows Verify can be called only on mocks.验证只能在模拟上调用。 viewModel is an instance, hence, the moment the execution hits that line, your test will fail. viewModel是一个实例,因此,一旦执行到该行,您的测试就会失败。

For unit testing LiveData, you might need the following rule对于 LiveData 单元测试,您可能需要以下规则

@get:Rule
var rule: TestRule = InstantTaskExecutorRule() 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 livedata 进行 Android 视图模型单元测试 - Android viewmodel unit test with livedata 使用 Repository + LiveData + Coroutines 进行 ViewModel 单元测试 - ViewModel unit testing with Repository + LiveData + Coroutines ViewModel中的LiveData如何使用转换观察存储库中的Livedata? - How can LiveData in ViewModel observe the Livedata in Repository using Transformations? 如何在 viewModel 中测试从存储库返回 LiveData 值的方法 - How to test a method in viewModel which returns LiveData value from repository LiveData,MVVM和存储库模式 - LiveData, MVVM and Repository Pattern 使用 RxJava 时对 LiveData 进行本地单元测试 - Local unit test for LiveData while using RxJava 使用 java 在 android 中使用存储库实现 ViewModel 和 LiveData 的正确方法? - Correct way to implement ViewModel and LiveData with Repository in android using java? 如何使用 viewmodel、repository 和 livedata 访问 room db - How to access room db using viewmodel, repository, and livedata 将 LiveData/ViewModel 与片段一起使用? - Using LiveData/ViewModel with Fragments? 使用 livedata、改造、mvvm 和存储库模式制作通用网络适配器 - Making a generic network adapter using livedata, retrofit, mvvm and repository pattern
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM