Koin: Espresso testing with koin

Created on 12 Oct 2018  路  1Comment  路  Source: InsertKoinIO/koin

Hello guys. So I'm trying to setup some UI tests with koin (greate library btw). It's basic a login screen. I'm using the mvvm patter so I'm making use of the koin-viewmodel library. The thing is I can't it it to work.
Here's my ViewModel class:

class LoginViewModel
internal constructor(application: Application, val auth: Auth, val preferences: Preferences,
                     val answers: Answers) :
        BaseAndroidViewModel(application) {

    val isLoading = MutableLiveData<Boolean>()

    var username = ""

    var password = ""

    val background = MutableLiveData<Drawable>()

    val goToMain = SingleLiveEvent<Void>()

    val task = SetBackground(getApplication())

    private val backgroundObserver = Observer<Drawable> {
        background.postValue(it)
    }

    init {
        task.background.observeForever(backgroundObserver)
    }

    override fun onCleared() {
        super.onCleared()
        task.background.removeObserver(backgroundObserver)
    }

    fun start() {
        setBackgroundImage()
//        repository.clear()
    }

    fun validateCredentials(view: View?) {
        if (username.isBlank() || password.isBlank()) {
            showSnackbar("Please fill both fields")
            return
        }

//        UIUtils.hideKeyboard(getApplication());
        if (!isNetworkUp()) {
            showSnackbar("No internet connection")
            return
        }

        isLoading.value = true
        authenticate()
    }

    private fun isNetworkUp(): Boolean {
        return NetworkUtils.isNetworkConnected(getApplication())
    }

    private fun authenticate() {
        auth.authenticate(username, password) { success, message ->
            isLoading.postValue(false)
            if (success) {
                showSnackbarBackground("Success")
                preferences.setString("username", username)
                preferences.setString("password", password)
                preferences.setBoolean("loggedIn", true)
                answers.logLogin(LoginEvent().putSuccess(true))
                goToMain.postValue(null)
            } else {
                showSnackbarBackground(message)
            }
        }
    }

    private fun setBackgroundImage() {
        task.execute()
    }


}

And here's my login activity:

class LoginActivity : BaseActivity<LoginViewModel>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
        bindScope(getOrCreateScope("login"))
        val binding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
        viewModel = getViewModel()
        binding.viewModel = viewModel
        binding.setLifecycleOwner(this)
        viewModel.goToMain.observe(this, Observer {
            startActivity(Intent(this@LoginActivity, MainActivity::class.java))
            finish()
        })
    }


    override fun onStart() {
        super.onStart()
        viewModel.start()
    }

    override fun getRoot(): View {
        return findViewById(R.id.container)
    }

}

Finally, here's my LoginActivityTest:

@RunWith(AndroidJUnit4::class)
@LargeTest
class LoginActivityTest : KoinTest {

    val auth: Auth by inject()

    @get:Rule
    val instantRule = InstantTaskExecutorRule()

    @get:Rule
    var activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)


    @Before
    fun setup() {
        loadKoinModules(module{
            viewModel<LoginViewModel>()
            single(override = true) { Auth.getInstance(null) }
        })
//        declareMock<LoginViewModel>()
        declareMock<Auth>()
    }

    @After
    fun tearDown() {
        stopKoin()
    }

    @Test
    fun blankFields() {
        activityRule.launchActivity(null)
        onView(ViewMatchers.withId(R.id.login)).perform(click())

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Please fill both fields")))
                .check(matches(isDisplayed()))
    }

    @Test
    fun invalidCredentials() {
        val captor = ArgumentCaptor.forClass(Auth.AuthCompletedListener::class.java)
        doAnswer {
            (it.arguments[0] as Auth.AuthCompletedListener).onCompleted(false, "Invalid username or password")
        }.`when`(auth).authenticate("", "", captor.capture())
        activityRule.launchActivity(null)
//        `when`(activityRule.activity.viewModel.validateCredentials(null)).then { doNothing() }
        onView(withId(R.id.username)).perform(typeText("dsaf"), closeSoftKeyboard())
        onView(withId(R.id.password)).perform(typeText("dssdfsaf"), closeSoftKeyboard())
        onView(withId(R.id.login)).perform(click())

        onView(withId(R.id.progressBar))
                .check(matches(isDisplayed()))

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Invalid username or password")))
                .check(matches(isDisplayed()))
    }

}

I can't seem to get it to work with the above code. I don't know I'm missing. I'd really appreciate it if someone could point me in the right direction.

Thanks in advance.

Most helpful comment

Got it to work with the following code:

@RunWith(AndroidJUnit4::class)
@LargeTest
class LoginActivityTest : KoinTest {

    val auth: Auth by inject()

    @get:Rule
    val instantRule = InstantTaskExecutorRule()

    @get:Rule
    var activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)


    @Before
    fun setup() {
        loadKoinModules(listOf(module{
            viewModel<LoginViewModel>(override = true)
        }))
        declareMock<Auth>()
    }

    @After
    fun tearDown() {
//        stopKoin()
    }

    @Test
    fun blankFields() {
        activityRule.launchActivity(null)
        onView(ViewMatchers.withId(R.id.login)).perform(click())

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Please fill both fields")))
                .check(matches(isDisplayed()))
    }

    @Test
    fun invalidCredentials() {
        val captor = ArgumentCaptor.forClass(Auth.AuthCompletedListener::class.java)
        `when`(auth.authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), captor.capture())).thenAnswer {
            (it.arguments[2] as Auth.AuthCompletedListener).onCompleted(false, "Invalid username or password")
        }

        activityRule.launchActivity(null)
        onView(withId(R.id.username)).perform(typeText("dsaf"), closeSoftKeyboard())
        onView(withId(R.id.password)).perform(typeText("dssdfsaf"), closeSoftKeyboard())
        onView(withId(R.id.login)).perform(click())

//        onView(withId(R.id.progressBar))
//                .check(matches(isDisplayed()))

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Invalid username or password")))
                .check(matches(isDisplayed()))
    }


}

I also had to update my gradle with the following dependencies:

androidTestImplementation 'org.mockito:mockito-android:2.22.0'
androidTestImplementation "org.koin:koin-test:$koin_version"

since koin does not use mockito-android by default. This resulted in two different versions of MockMaker so I had add the following snippet to make it work:

packagingOptions {
    pickFirst 'mockito-extensions/org.mockito.plugins.MockMaker'
}

Boy! It's been loong weekend :)

>All comments

Got it to work with the following code:

@RunWith(AndroidJUnit4::class)
@LargeTest
class LoginActivityTest : KoinTest {

    val auth: Auth by inject()

    @get:Rule
    val instantRule = InstantTaskExecutorRule()

    @get:Rule
    var activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)


    @Before
    fun setup() {
        loadKoinModules(listOf(module{
            viewModel<LoginViewModel>(override = true)
        }))
        declareMock<Auth>()
    }

    @After
    fun tearDown() {
//        stopKoin()
    }

    @Test
    fun blankFields() {
        activityRule.launchActivity(null)
        onView(ViewMatchers.withId(R.id.login)).perform(click())

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Please fill both fields")))
                .check(matches(isDisplayed()))
    }

    @Test
    fun invalidCredentials() {
        val captor = ArgumentCaptor.forClass(Auth.AuthCompletedListener::class.java)
        `when`(auth.authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), captor.capture())).thenAnswer {
            (it.arguments[2] as Auth.AuthCompletedListener).onCompleted(false, "Invalid username or password")
        }

        activityRule.launchActivity(null)
        onView(withId(R.id.username)).perform(typeText("dsaf"), closeSoftKeyboard())
        onView(withId(R.id.password)).perform(typeText("dssdfsaf"), closeSoftKeyboard())
        onView(withId(R.id.login)).perform(click())

//        onView(withId(R.id.progressBar))
//                .check(matches(isDisplayed()))

        onView(allOf<View>(withId(com.google.android.material.R.id.snackbar_text), withText("Invalid username or password")))
                .check(matches(isDisplayed()))
    }


}

I also had to update my gradle with the following dependencies:

androidTestImplementation 'org.mockito:mockito-android:2.22.0'
androidTestImplementation "org.koin:koin-test:$koin_version"

since koin does not use mockito-android by default. This resulted in two different versions of MockMaker so I had add the following snippet to make it work:

packagingOptions {
    pickFirst 'mockito-extensions/org.mockito.plugins.MockMaker'
}

Boy! It's been loong weekend :)

Was this page helpful?
0 / 5 - 0 ratings