Shopify / android-testify

Add screenshots to your Android tests
https://testify.dev
MIT License
231 stars 23 forks source link

Question: Is it possible pass data viewModel with TestifyLayout? #260

Closed rdesai-handy closed 2 years ago

DanielJette commented 2 years ago

Hello @rdesai-handy

Thanks for the question. It is possible to modify the data a ViewModel consumes or is initialized with. However, it largely depends on how you are creating your ViewModel.

Are you using any form of dependency injection with your ViewModels? How this is handled depends somewhat on how you create your ViewModels.

Are you using a mocking library such as mockito-android? This can greatly simplify the modification of data consumed by a ViewModel.

As a best practice, it is recommend to focus the Testify testing on the view-layer: the View, Compose, Fragment or Activity and the way that they render. When testing the view-layer, an entity such as the ViewModel can be considered to be external dependencies to your tests and so you can focus on coercing your views to render the state you desire through any means available. When it comes to ViewModels, a good strategy is to use a stub or mock of your ViewModel instead of a real instance. Then, you can set the ViewModel to return any values you desire.

Do you have an example of what you'd like to accomplish?

In my personal project, I accomplish this by first creating a custom subclass of ScreenshotRule. In this subclass, I override fun beforeAssertSame() and add a method setViewModelState. With these, I can set up the ViewModel state prior to taking my screenshot. Here is a rough example of what this looks like:

class CustomScreenshotRule<TViewModel> : ScreenshotRule<ScreenshotTestActivity>() {

    private var viewModelStateModifications: ((TViewModel) -> Unit)? = null

    fun setViewModelState(viewModelStateModifications: (TViewModel) -> Unit): CustomScreenshotRule<TViewModel> {
        this.viewModelStateModifications = viewModelStateModifications
        return this
    }

    override fun beforeAssertSame() {
        super.beforeAssertSame()
        InstrumentationRegistry.getInstrumentation().runOnMainSync {
            val extras = fragmentNavigation?.extras ?: viewModelExtras()
            viewModelSpy.createViewModel(extras).let { viewModel ->
                viewModelStateModifications?.let {
                    it(viewModelSpy.viewModel)
                }
            }
        }
    }
}

And then your test would look something like:

class MyTest {
    val screenshotTestRule = CustomScreenshotRule<MyViewModel>()

    @ScreenshotInstrumentation
    @Test
    fun default() {
        screenshotTestRule
            .setViewModelState { viewModel ->
                doReturn(5).whenever(viewModel).sum()
            }
            .assertSame()
    }
}
rdesai-handy commented 2 years ago

Thanks @DanielJette for detailed example 👍