如何告诉 composeTestRule 等待 navhost 转换?
How to tell the composeTestRule to wait for the navhost transition?
我正在尝试为完全用 Compose 编写的 Android 应用程序编写集成测试,该应用程序只有一个 Activity 并使用 Compose Navigation 更改屏幕内容。
我设法正确交互并测试了导航图显示的第一个屏幕,但是,当我导航到新目的地时,测试失败了,因为它没有等待 NavHost 加载新内容.
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun appStartsWithoutCrashing() {
composeTestRule.apply {
// Check Switch
onNodeWithTag(FirstScreen.CONSENT_SWITCH)
.assertIsDisplayed()
.assertIsOff()
.performClick()
.assertIsOn()
// Click accept button
onNodeWithTag(FirstScreen.ACCEPT_BUTTON)
.assertIsDisplayed()
.performClick()
// Check we are inside the second screen
onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD)
.assertIsDisplayed()
}
}
}
我确定这是时间问题,因为如果我在 onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD).assertIsDisplayed()
之前添加 Thread.sleep(500)
,测试就会成功。但我想在我的代码中避免使用 Thread.sleep()
s。
有没有更好的方法告诉 composeTestRule
在执行 assertIsDisplayed()
之前等待 NavHost 加载新内容?
PS
我知道单独测试可组合项会更好,但我真的想使用 Espresso 模拟应用程序上的用户输入,而不仅仅是测试可组合项的行为。
如this very informative blog article中所建议,waitUntil
可用于等待显示具有正确标签的节点:
// Waiting for the new destination to be shown
waitUntil {
composeTestRule
.onAllNodesWithTag(LogInTestTags.USERNAME_TEXT_FIELD)
.fetchSemanticsNodes().size == 1
}
或者,添加一些糖后:
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun appStartsWithoutCrashing() {
composeTestRule.apply {
// Check Switch
onNodeWithTag(FirstScreen.CONSENT_SWITCH)
.assertIsDisplayed()
.assertIsOff()
.performClick()
.assertIsOn()
// Click accept button
onNodeWithTag(FirstScreen.ACCEPT_BUTTON)
.assertIsDisplayed()
.performClick()
// Waiting for the new destination to be shown
waitUntilExists(hasTestTag(SecondScreen.USERNAME_TEXT_FIELD))
// Check we are inside the second screen
onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD)
.assertIsDisplayed()
}
}
}
private const val WAIT_UNTIL_TIMEOUT = 1_000L
fun ComposeContentTestRule.waitUntilNodeCount(
matcher: SemanticsMatcher,
count: Int,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) {
waitUntil(timeoutMillis) {
onAllNodes(matcher).fetchSemanticsNodes().size == count
}
}
fun ComposeContentTestRule.waitUntilExists(
matcher: SemanticsMatcher,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) = waitUntilNodeCount(matcher, 1, timeoutMillis)
fun ComposeContentTestRule.waitUntilDoesNotExist(
matcher: SemanticsMatcher,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) = waitUntilNodeCount(matcher, 0, timeoutMillis)
我正在尝试为完全用 Compose 编写的 Android 应用程序编写集成测试,该应用程序只有一个 Activity 并使用 Compose Navigation 更改屏幕内容。
我设法正确交互并测试了导航图显示的第一个屏幕,但是,当我导航到新目的地时,测试失败了,因为它没有等待 NavHost 加载新内容.
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun appStartsWithoutCrashing() {
composeTestRule.apply {
// Check Switch
onNodeWithTag(FirstScreen.CONSENT_SWITCH)
.assertIsDisplayed()
.assertIsOff()
.performClick()
.assertIsOn()
// Click accept button
onNodeWithTag(FirstScreen.ACCEPT_BUTTON)
.assertIsDisplayed()
.performClick()
// Check we are inside the second screen
onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD)
.assertIsDisplayed()
}
}
}
我确定这是时间问题,因为如果我在 onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD).assertIsDisplayed()
之前添加 Thread.sleep(500)
,测试就会成功。但我想在我的代码中避免使用 Thread.sleep()
s。
有没有更好的方法告诉 composeTestRule
在执行 assertIsDisplayed()
之前等待 NavHost 加载新内容?
PS 我知道单独测试可组合项会更好,但我真的想使用 Espresso 模拟应用程序上的用户输入,而不仅仅是测试可组合项的行为。
如this very informative blog article中所建议,waitUntil
可用于等待显示具有正确标签的节点:
// Waiting for the new destination to be shown
waitUntil {
composeTestRule
.onAllNodesWithTag(LogInTestTags.USERNAME_TEXT_FIELD)
.fetchSemanticsNodes().size == 1
}
或者,添加一些糖后:
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun appStartsWithoutCrashing() {
composeTestRule.apply {
// Check Switch
onNodeWithTag(FirstScreen.CONSENT_SWITCH)
.assertIsDisplayed()
.assertIsOff()
.performClick()
.assertIsOn()
// Click accept button
onNodeWithTag(FirstScreen.ACCEPT_BUTTON)
.assertIsDisplayed()
.performClick()
// Waiting for the new destination to be shown
waitUntilExists(hasTestTag(SecondScreen.USERNAME_TEXT_FIELD))
// Check we are inside the second screen
onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD)
.assertIsDisplayed()
}
}
}
private const val WAIT_UNTIL_TIMEOUT = 1_000L
fun ComposeContentTestRule.waitUntilNodeCount(
matcher: SemanticsMatcher,
count: Int,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) {
waitUntil(timeoutMillis) {
onAllNodes(matcher).fetchSemanticsNodes().size == count
}
}
fun ComposeContentTestRule.waitUntilExists(
matcher: SemanticsMatcher,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) = waitUntilNodeCount(matcher, 1, timeoutMillis)
fun ComposeContentTestRule.waitUntilDoesNotExist(
matcher: SemanticsMatcher,
timeoutMillis: Long = WAIT_UNTIL_TIMEOUT
) = waitUntilNodeCount(matcher, 0, timeoutMillis)