Koin: How to inject mocked ViewModel into Activity for espresso test ?

Created on 20 Aug 2018  路  5Comments  路  Source: InsertKoinIO/koin

I'm trying to inject mocked ViewModel into Activity for espresso test but activity is getting regular object.

My ActivityTest is able to create required mock but i'm not sure how to access that mock in Activity.
complete app source

val MovieListModule: Module = module {
    single {
        DummyMovieListRepository() as MovieListRepository
    }

    viewModel {
        MovieListViewModel(respository = get())
    }
}
@RunWith(AndroidJUnit4::class)
class HomeScreenActivityTest : KoinTest {
    @Rule
    @JvmField
    val rule = ActivityTestRule(HomeScreenActivity::class.java, true, true)
    val viewModel: MovieListViewModel by inject()

    @Before
    fun setup() {
        declareMock<MovieListViewModel>(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")))
    }
}
question

Most helpful comment

I'm able to provide mocked viewModel this way

@RunWith(AndroidJUnit4::class)
class HomeScreenActivityTest : KoinTest {
    @Rule
    @JvmField
    val rule = ActivityTestRule(HomeScreenActivity::class.java, true, false)

    lateinit var mockVm: MovieListViewModel

    @Before
    fun setup() {
        mockVm = mock(MovieListViewModel::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_homescreen_message))
                .check(matches(isDisplayed()))

        onView(withId(R.id.tv_homescreen_message))
                .check(matches(withText(message)))
    }
}

on the side note, I started koin with empty module so we can declare only needed dependencies from Test class

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

All 5 comments

Yes, it should works. This is the exact way of doing it 馃憤 !

But this doesn't work

Doesn't work for me too. For me works just: declareMock<MovieListViewModel>m but when I try to do e.g. Mockito.verify(viewModel).loadUsers() (assuming that viewModel has this method, I get exception:

org.koin.error.DependencyResolutionException: Multiple definitions found for type...., When I do not touch viewModel, test passes

@arnaudgiuliani are you sure this works in instrumented tests? Isn't there a limitation of not being able to mock final classes?

I tried this approach and I get an exception which looks like the limitation of not being able to
mock final classes on Android JVM:

org.koin.error.BeanInstanceCreationException: Can't create definition for 'Single [name='AccountsListService',class='com.exampe.service.AccountsListService', binds~(com.example.service.AccountsListServiceInterface)]' due to error :
Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null)

Although to be fair I don't see a final class since I'm mocking an interface.
Any ideas?

I'm able to provide mocked viewModel this way

@RunWith(AndroidJUnit4::class)
class HomeScreenActivityTest : KoinTest {
    @Rule
    @JvmField
    val rule = ActivityTestRule(HomeScreenActivity::class.java, true, false)

    lateinit var mockVm: MovieListViewModel

    @Before
    fun setup() {
        mockVm = mock(MovieListViewModel::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_homescreen_message))
                .check(matches(isDisplayed()))

        onView(withId(R.id.tv_homescreen_message))
                .check(matches(withText(message)))
    }
}

on the side note, I started koin with empty module so we can declare only needed dependencies from Test class

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"
    }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dakuenjery picture dakuenjery  路  4Comments

hkelidari picture hkelidari  路  3Comments

pchmielowski picture pchmielowski  路  3Comments

guymclean picture guymclean  路  3Comments

sankarsana picture sankarsana  路  4Comments