androidandroid-espressoandroid-jetpackui-testingandroid-jetpack-compose

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


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)))
}

Solution

  • 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.