How to enter text in Jetpack compose TextField through UI tests?

Viewed 2550

In Jetpack compose I have a TextField and I'm trying to write Espresso UI tests.
I didn't find how I can enter text in the TextField, any ideas, please?

TextField(
    value = textState.value,
    modifier = Modifier.fillMaxWidth(),
    onValueChange = {
        textState.value = it
        apiServiceCall(textState.value.text)
    },
    keyboardOptions = KeyboardOptions(
        capitalization = KeyboardCapitalization.Sentences)
    ),
)

@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Test
fun enterTextAndMakeServiceCall() {
    ActivityScenario.launch(MainActivity::class.java)

    // TODO: Enter text inside the TextField
    composeTestRule.onNode(hasText(getString(R.string.result)))
}
2 Answers

I first set the testTag modifier on the composable I want to test:

const val MY_TEXTFIELD_TAG = "myTextFieldTag"

TextField(
    value = textState.value,
    modifier = Modifier.fillMaxWidth().testTag(MY_TEXTFIELD_TAG),
    onValueChange = {
        textState.value = it
    },
    keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
)

And then from your test you can set and check the value like this:

@Test
fun setAndCheckTheTextFieldValue() {
    ActivityScenario.launch(MainActivity::class.java)
    val resultText = "result"

    // Sets the TextField value
    composeTestRule.onNodeWithTag(MY_TEXTFIELD_TAG).performTextInput(resultText)

    // Asserts the TextField has the corresponding value
    composeTestRule.onNodeWithTag(MY_TEXTFIELD_TAG).assert(hasText(resultText))
}

UPDATE:

Another way I use lately is to use the contentDescription instead.

Let's say you have a TextField with content description like this one (not using state hoisting for simplicity on this sample):

@Composable
fun MyTextField() {
    val textState = remember { mutableStateOf(TextFieldValue()) }
    val textFieldContentDescription = stringResource(id = R.string.text_field_content_description)
    TextField(
        value = textState.value,
        modifier = Modifier
            .fillMaxWidth()
            .semantics { contentDescription = textFieldContentDescription },
        onValueChange = {
            textState.value = it
        },
    )
}

The test could be something like:

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun setAndCheckTheTextFieldValue() {
    lateinit var textFieldContentDescription: String
    composeTestRule.setContent {
        textFieldContentDescription = stringResource(id = R.string.text_field_content_description)
        MaterialTheme {
            MyTextField()
        }
    }
    val resultText = "result"

    // Sets the TextField value
    composeTestRule.onNodeWithContentDescription(textFieldContentDescription).performTextInput(resultText)

    // Asserts the TextField has the corresponding value
    composeTestRule.onNodeWithContentDescription(textFieldContentDescription).assert(hasText(resultText, ignoreCase = true))
}

and this way the app is more accessible as well by having content descriptions.

Yo just can find by nodeText you need to search the string in label property

composeTestRule.onNodeWithText("user").performTextInput("userCred")

That will find that Textfield and will enter the text "userCred".

This solution only works if you don't have any other text that matches that string.

Related