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.
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 :)
Most helpful comment
Got it to work with the following code:
I also had to update my gradle with the following dependencies:
since koin does not use mockito-android by default. This resulted in two different versions of
MockMakerso I had add the following snippet to make it work:Boy! It's been loong weekend :)