Search code examples
androidkotlinandroid-espressoandroid-viewmodelkoin

Koin - how to provide mock ViewModel for espresso test?


How can we inject mocked viewModel into Activity for espresso test? Using declareMock I get mock object in Test class but Activity receives regular viewModel object.

@RunWith(AndroidJUnit4::class)
class SomeActivityTest : KoinTest {
    @Rule
    @JvmField
    val rule = ActivityTestRule(SomeActivity::class.java, true, true)
    val viewModel: MyViewModel by inject()

    @Before
    fun setup() {
        declareMock<MyViewModel>(isFactory = true, binds = listOf(ViewModel::class))
    }

    @After
    fun cleanUp() {
        stopKoin()
    }

    @Test
    fun shouldHaveTextViewVisible() {
        `when`(viewModel.sayHello())
                .thenReturn("hello view-model")
        onView(withId(R.id.tv_homescreen_message))
                .check(matches(isDisplayed()))
        onView(withId(R.id.tv_homescreen_message))
                .check(matches(withText("hello view-model")))
    }
}

Solution

  • In this case, Espresso test were still using main application class which declares all koin module required for app.

    Starting koin without any modules, allows us to load only required modules during test.

    // application class for espresso tests
    class TestApp : Application() {
        override fun onCreate() {
            super.onCreate()
            startKoin(this, emptyList())
        }
    }
    
    class TestAppJUnitRunner : AndroidJUnitRunner() {
        override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
            return super.newApplication(cl, TestApp::class.java.name, context)
        }
    }
    
    // app module build.gradle
    android {
        defaultConfig {
            testInstrumentationRunner "com.package.TestAppJUnitRunner"
        }
    }
    

    It is important to declare mock method before starting activity

    @RunWith(AndroidJUnit4::class)
    class SomeActivityTest : KoinTest {
        @Rule
        @JvmField
        val rule = ActivityTestRule(SomeActivity::class.java, true, false)
    
        lateinit var mockVm: MyViewModel
    
    
        @Before
        fun setup() {
            mockVm = mock(MyViewModel::class.java)
    
            loadKoinModules(module {
                viewModel {
                    mockVm
                }
            })
        }
    
        @After
        fun cleanUp() {
            stopKoin()
        }
    
        @Test
        fun shouldHaveTextViewWithMessage() {
            // 1. declare mock method
            val message = "hello view-model"
            Mockito.`when`(mockVm.sayHello())
                    .thenReturn(message)
    
            // 2. start activity
            rule.launchActivity(null)
    
    
            // 3. test
            onView(withId(R.id.tv_message))
                    .check(matches(isDisplayed()))
    
            onView(withId(R.id.tv_message))
                    .check(matches(withText(message)))
        }
    }
    

    Sample code