Skip to content

Commit

Permalink
♻️ Update Tests for better Resource Idling
Browse files Browse the repository at this point in the history
Removing all the `Thread.sleep()` in favor of Compose "waitUntil()" for
better handling idling resources. The solution here is based on the
excellent article from Jose Alcérreca.

https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473
  • Loading branch information
igorescodro committed Oct 12, 2022
1 parent 5688f39 commit 2772cff
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.escodro.alkaa
import android.app.Notification
import androidx.annotation.StringRes
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
Expand All @@ -16,6 +17,8 @@ import com.escodro.local.model.Task
import com.escodro.local.provider.DaoProvider
import com.escodro.task.R
import com.escodro.test.DisableAnimationsRule
import com.escodro.test.waitUntilExists
import com.escodro.test.waitUntilNotExists
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.After
Expand Down Expand Up @@ -96,6 +99,9 @@ internal class NotificationFlowTest : KoinTest {
// Run the PendingIntent in the notification
notificationManager!!.activeNotifications.first().notification.contentIntent.send()

// Wait until the title is displayed
composeTestRule.waitUntilExists(hasText(name))

// Validate the task detail was opened
composeTestRule.onNodeWithText(name).assertIsDisplayed()
composeTestRule
Expand Down Expand Up @@ -151,8 +157,10 @@ internal class NotificationFlowTest : KoinTest {
// Run the PendingIntent in the "Done" action button
notificationManager!!.activeNotifications.first().notification.actions[1].actionIntent.send()

// Wait until the task is complete and no longer visible
composeTestRule.waitUntilNotExists(hasText(name))

// Validate the task is now updated as "completed"
Thread.sleep(300)
val task = daoProvider.getTaskDao().getTaskById(id)

assertTrue(task!!.completed)
Expand Down
9 changes: 7 additions & 2 deletions app/src/androidTest/java/com/escodro/alkaa/SearchFlowTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.escodro.alkaa

import androidx.annotation.StringRes
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
Expand All @@ -14,6 +15,8 @@ import com.escodro.alkaa.navigation.NavGraph
import com.escodro.designsystem.AlkaaTheme
import com.escodro.local.provider.DaoProvider
import com.escodro.test.DisableAnimationsRule
import com.escodro.test.waitUntilExists
import com.escodro.test.waitUntilNotExists
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
Expand Down Expand Up @@ -75,7 +78,8 @@ internal class SearchFlowTest : KoinTest {
onNode(hasSetTextAction()).performTextInput(query)
onAllNodesWithText(text = query, useUnmergedTree = true)[1].assertExists()

Thread.sleep(1000)
// Wait until the task is shown
waitUntilExists(hasText(FAKE_TASKS[0].title))

// Drop the first task and validate others are not shown
FAKE_TASKS.drop(1).forEach { task ->
Expand All @@ -90,7 +94,8 @@ internal class SearchFlowTest : KoinTest {
with(composeTestRule) {
onNode(hasSetTextAction()).performTextInput("query")

Thread.sleep(1000)
// Wait until the first task is not visible
waitUntilNotExists(hasText(FAKE_TASKS[0].title))

FAKE_TASKS.forEach { task ->
// Validate all tasks are shown
Expand Down
6 changes: 5 additions & 1 deletion app/src/androidTest/java/com/escodro/alkaa/TaskFlowTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.escodro.alkaa
import androidx.annotation.StringRes
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
Expand All @@ -11,6 +12,7 @@ import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTextReplacement
import androidx.test.platform.app.InstrumentationRegistry
import com.escodro.alkaa.fake.CoroutinesDebouncerFake
import com.escodro.alkaa.model.HomeSection
import com.escodro.alkaa.navigation.NavGraph
import com.escodro.core.coroutines.CoroutineDebouncer
import com.escodro.designsystem.AlkaaTheme
Expand All @@ -19,6 +21,7 @@ import com.escodro.local.provider.DaoProvider
import com.escodro.test.DisableAnimationsRule
import com.escodro.test.Events
import com.escodro.test.onChip
import com.escodro.test.waitUntilExists
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
Expand Down Expand Up @@ -188,6 +191,7 @@ internal class TaskFlowTest : KoinTest {
).performClick()

// Wait the list to be loaded
Thread.sleep(1000)
val taskTitle = context.getString(HomeSection.Tasks.title)
composeTestRule.waitUntilExists(hasText(taskTitle))
}
}
2 changes: 1 addition & 1 deletion libraries/test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies {

api(libs.coroutines.test)

implementation(libs.compose.uitest) {
implementation(libs.bundles.composetest) {
exclude(group = "androidx.core", module = "core-ktx")
exclude(group = "androidx.fragment", module = "fragment")
exclude(group = "androidx.customview", module = "customview")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.escodro.test

import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.junit4.ComposeContentTestRule

/**
* Waits until the given matcher satisfied or until the [timeoutMillis] is reached.
*
* See: https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473
*/
fun ComposeContentTestRule.waitUntilExists(
matcher: SemanticsMatcher,
timeoutMillis: Long = 3_000L
) = waitUntilNodeCount(matcher = matcher, count = 1, timeoutMillis = timeoutMillis)

fun ComposeContentTestRule.waitUntilNotExists(
matcher: SemanticsMatcher,
timeoutMillis: Long = 3_000L
) = waitUntilNodeCount(matcher = matcher, count = 0, timeoutMillis = timeoutMillis)

private fun ComposeContentTestRule.waitUntilNodeCount(
matcher: SemanticsMatcher,
count: Int,
timeoutMillis: Long = 3_000L
) = waitUntil(timeoutMillis) {
onAllNodes(matcher).fetchSemanticsNodes().size == count
}

0 comments on commit 2772cff

Please sign in to comment.