How to correctly mock ViewModel on androidTest

后端 未结 4 2107
野性不改
野性不改 2021-02-13 03:21

I\'m currently writing some UI unit tests for a fragment, and one of these @Test is to see if a list of objects is correctly displayed, this is not an integ

相关标签:
4条回答
  • 2021-02-13 03:39

    Within your test setup you'll need to provide a test version of the FavoritesViewModelFactory which is being injected in the Fragment.

    You could do something like the following, where the Module will need to be added to your TestAppComponent:

    @Module
    object TestFavoritesViewModelModule {
    
        val viewModelFactory: FavoritesViewModelFactory = mock()
    
        @JvmStatic
        @Provides
        fun provideFavoritesViewModelFactory(): FavoritesViewModelFactory {
            return viewModelFactory
        }
    }
    

    You'd then be able to provide your Mock viewModel in the test.

    fun setupViewModelFactory() {
        whenever(TestFavoritesViewModelModule.viewModelFactory.create(FavoritesViewModel::class.java)).thenReturn(viewModel)
    }
    
    0 讨论(0)
  • 2021-02-13 03:39

    I have solved this problem using an extra object injected by Dagger, you can find the full example here: https://github.com/fabioCollini/ArchitectureComponentsDemo

    In the fragment I am not using directly the ViewModelFactory, I have defined a custom factory defined as a Dagger singleton: https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearch/src/main/java/it/codingjam/github/ui/search/SearchFragment.kt

    Then in the test I replace using DaggerMock this custom factory using a factory that always returns a mock instead of the real viewModel: https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearchTest/src/androidTest/java/it/codingjam/github/ui/repo/SearchFragmentTest.kt

    0 讨论(0)
  • 2021-02-13 03:49

    In the example you provided, you are using mockito to return a mock for a specific instance of your view model, and not for every instance.

    In order to make this work, you will have to have your fragment use the exact view model mock that you have created.

    Most likely this would come from a store or a repository, so you could put your mock there? It really depends on how you setup the acquisition of the view model in your Fragments logic.

    Recommendations: 1) Mock the data sources the view model is constructed from or 2) add a fragment.setViewModel() and Mark it as only for use in tests. This is a little ugly, but if you don't want to mock data sources, it is pretty easy this way.

    0 讨论(0)
  • 2021-02-13 03:59

    Look like, you use kotlin and koin(1.0-beta). It is my decision for mocking

    @RunWith(AndroidJUnit4::class)
    class DashboardFragmentTest : KoinTest {
    @Rule
    @JvmField
    val activityRule = ActivityTestRule(SingleFragmentActivity::class.java, true, true)
    @Rule
    @JvmField
    val executorRule = TaskExecutorWithIdlingResourceRule()
    @Rule
    @JvmField
    val countingAppExecutors = CountingAppExecutorsRule()
    
    private val testFragment = DashboardFragment()
    
    private lateinit var dashboardViewModel: DashboardViewModel
    private lateinit var router: Router
    
    private val devicesSuccess = MutableLiveData<List<Device>>()
    private val devicesFailure = MutableLiveData<String>()
    
    @Before
    fun setUp() {
        dashboardViewModel = Mockito.mock(DashboardViewModel::class.java)
        Mockito.`when`(dashboardViewModel.devicesSuccess).thenReturn(devicesSuccess)
        Mockito.`when`(dashboardViewModel.devicesFailure).thenReturn(devicesFailure)
        Mockito.`when`(dashboardViewModel.getDevices()).thenAnswer { _ -> Any() }
    
        router = Mockito.mock(Router::class.java)
        Mockito.`when`(router.loginActivity(activityRule.activity)).thenAnswer { _ -> Any() }
    
        StandAloneContext.loadKoinModules(hsApp + hsViewModel + api + listOf(module {
            single(override = true) { router }
            factory(override = true) { dashboardViewModel } bind ViewModel::class
        }))
    
        activityRule.activity.setFragment(testFragment)
        EspressoTestUtil.disableProgressBarAnimations(activityRule)
    }
    
    @After
    fun tearDown() {
        activityRule.finishActivity()
        StandAloneContext.closeKoin()
    }
    
    @Test
    fun devicesSuccess(){
        val list = listOf(Device(deviceName = "name1Item"), Device(deviceName = "name2"), Device(deviceName = "name3"))
        devicesSuccess.postValue(list)
        onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
        onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name1Item"))))
        onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name2"))))
        onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name3"))))
    }
    
    @Test
    fun devicesFailure(){
        devicesFailure.postValue("error")
        onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
        Mockito.verify(router, times(1)).loginActivity(testFragment.activity!!)
    }
    
    @Test
    fun devicesCall() {
        onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
        Mockito.verify(dashboardViewModel, Mockito.times(1)).getDevices()
    }
    

    }

    0 讨论(0)
提交回复
热议问题