I’m setting up an androidTest to run a click test to make sure the login flow is always working.
The test is done in terms of opening the screen, press login button, enter email/password and then the login for real button is called. But here is the issue. My test don’t wait for the viewModel to call .login(email, pass).collect()
stuff. So my sessionManager that should have a session now always respond with false.
So I tried setting up a test rule for a “MainDispatcherRule” to make sure all coroutines used that dispatcher. But doing that just make the Compose code fall in to a infinity never being idle state.
I’ve tried to use the advanceUntilIdle and different delays before the assertTrue in the end. I’ve not tested to mock the viewModel. Since all my API calls are fake with the MockApi anyway.
Any help would be awesome!
This is the test
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LoginUserTest : BaseTest(hasValidSession = false) {
private val sessionManager: SessionManager by inject()
/*@get:Rule
val mainRule = MainDispatcherRule(UnconfinedTestDispatcher())
*/
@Test
fun start_app_login_user() = runTest {
// Start screen login button
composeTestRule.onNodeWithTag(TestTagID.StartLoginButton).performClick()
// Login email screen
composeTestRule.onNodeWithTag(TestTagID.EmailLoginEmail).apply {
performTextInput("[email protected]")
performImeAction()
}
composeTestRule.onNodeWithTag(TestTagID.EmailLoginPassword).apply {
performTextInput("Hello123!!")
performImeAction()
}
// Make sure button is enabled and press it (viewModel.login will be called)
composeTestRule.onNodeWithTag(TestTagID.EmailLoginButton).apply {
assertIsEnabled()
performClick()
}
assertTrue { sessionManager.hasValidSession() }
}
}
As it is above the test will run, clicks will work etc but the last check for assertTrue { sessionManager.hasValidSession() }
returns false.
But If I enable the MainDispatcherRule
then the Compose UI part never get’s idle and a timeout happens.
The BaseTest is pretty basic
open class BaseTest(private val hasValidSession: Boolean = true) : AutoCloseKoinTest() {
private var scenario: ActivityScenario<MainActivity>? = null
@get:Rule
val composeTestRule = createAndroidComposeRule<ComposeTestActivity>()
@get:Rule
val mockProvider = MockProviderRule.create { mockkClass(it, relaxed = true) }
private val sessionManager: SessionManager by inject()
private val loadableRepository: LoadableRepo by inject()
private val idlingResources: Array<EspressoIdlingResource> by lazy { arrayOf(loadableRepository) }
private val composeIdlingResources: List<ComposeIdlingResource> by lazy {
idlingResources.map {
object : ComposeIdlingResource {
override val isIdleNow: Boolean get() = it.isIdleNow
}
}
}
@Before
open fun setUp() {
declare<MainApi> { MockApi(get()) } // Make sure all api calls in test is running mock endpoints
sessionManager.setAuthToken(AuthResponse.mock().takeIf { hasValidSession })
Locale.setDefault(Locale.US)
IdlingRegistry.getInstance().register(*idlingResources)
composeIdlingResources.forEach { composeTestRule.registerIdlingResource(it) }
scenario = ActivityScenario.launch(MainActivity::class.java)
}
@After
open fun tearDown() {
scenario?.close()
IdlingRegistry.getInstance().unregister(*idlingResources)
composeIdlingResources.forEach { composeTestRule.unregisterIdlingResource(it) }
}
}
The build.gradle.kts test options..
testOptions {
animationsDisabled = true
unitTests.apply {
isIncludeAndroidResources = true
isReturnDefaultValues = true
}
}