【问题标题】:Unit test ViewModel using repository pattern and LiveData使用存储库模式和 LiveData 对 ViewModel 进行单元测试
【发布时间】:2020-10-17 11:17:15
【问题描述】:

我想为我的视图模型类编写一个单元测试:

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

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

这是我的存储库类:

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))
        }
}

这是我的道课:

@Dao
interface ShowDao {

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

这是我的单元测试:

@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
    }
}

但我收到以下异常:

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)

如果您能给我任何指导,我将不胜感激。

【问题讨论】:

    标签: android android-livedata android-viewmodel


    【解决方案1】:

    我将 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>>
    }
    

    我可以成功运行我的测试,例如:

        @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)
            }
        }
    

    【讨论】:

      【解决方案2】:

      我会模拟存储库并指示 mockito true Mockito.when().doReturn() 返回一些数据,并验证 LiveData 输出是否正确。

      当然你可以使用实例ShowRepository。您仍然需要指示 mockito 在执行命中模拟对象时如何返回。和以前一样,您可以更改行为 w

      这一行是错误的verify(viewModel).shows。验证只能在模拟上调用。 viewModel 是一个实例,因此,一旦执行到该行,您的测试就会失败。

      对于 LiveData 的单元测试,您可能需要以下规则

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

      【讨论】:

      • getShows方法返回liveData。我应该在 doReturn() 中使用什么?我知道如果它是可观察的,我可以使用 Observable.just(response) 但是 liveData 怎么样?
      • Reposity.shows 也是一个 liveData。我应该在 doReturn() 中使用什么:Mockito.when(repository.shows).thenReturn(???)
      • repository.shows 返回的是什么? MyResult&lt;List&lt;Show&gt;&gt; ?
      • 它正在返回:LiveData>>
      猜你喜欢
      • 2021-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-28
      • 1970-01-01
      • 2014-02-26
      相关资源
      最近更新 更多