0

I have the following code:

ViewModel:

private val _prefix = MutableStateFlow("")
    val prefix: StateFlow<String> = _prefix
    private val _firstName = MutableStateFlow("")
    val firstName: StateFlow<String> = _firstName
    private val _middleName = MutableStateFlow("")
    val middleName: StateFlow<String> = _middleName
    private val _lastName = MutableStateFlow("")
    val lastName: StateFlow<String> = _lastName
    private val _suffix = MutableStateFlow("")
    val suffix: StateFlow<String> = _suffix

    fun saveContact(
        prefix: String,
        firstName: String,
        middleName: String,
        lastName: String,
        suffix: String,
    ) {
        _prefix.value = prefix
        _firstName.value = firstName
        _middleName.value = middleName
        _lastName.value = lastName
        _suffix.value = suffix
    }
}

I call saveContact from my view once all of the fields relating to the variables are entered.

Here's how I test the above works:

ViewTest:

@Test
    fun testView_success() {
        lateinit var viewModel: ViewModel

        ctr.setContent {
            viewModel =
                ViewModel(AppDatabase.getDatabase(LocalContext.current).dao)

            View(
                viewModel = viewModel,
                buttonActions = ViewButtonActions(
                    onNavigateBack = {},
                    onSave = {},
                )
            )
        }

        // Let's make sure that we are displaying the correct view.
        ctr.onNodeWithTag(tags.testTag).assertIsDisplayed()

        /*
         * Personal Details
         */
        // Let's now show all fields for each section.
        ctr.onNodeWithTag(tags.buttonShowHiddenContentPD).performClick()

        // Then we'll insert our text.
        ctr.onNodeWithTag(tags.textFieldPrefix).performTextInput(TestData.prefix)
        ctr.onNodeWithTag(tags.textFieldFirstName).performTextInput(TestData.firstName)
        ctr.onNodeWithTag(tags.textFieldMiddleName).performTextInput(TestData.middleName)
        ctr.onNodeWithTag(tags.textFieldLastName).performTextInput(TestData.lastName)
        ctr.onNodeWithTag(tags.textFieldSuffix).performTextInput(TestData.suffix)

        // Then tap the save button again.
        ctr.onNodeWithTag(tags.buttonSave)

        // Let's now check to see if all of the text has been passed on.
        assertEquals(
            TestData.prefix, // is "Mr"
            viewModel.prefix.value
        )
        assertEquals(
            TestData.firstName, // "Joe"
            viewModel.firstName.value
        )
        assertEquals(
            TestData.middleName, // "Ray"
            viewModel.middleName.value
        )
        assertEquals(
            TestData.lastName, // "Bloggs
            viewModel.lastName.value
        )
        assertEquals(
            TestData.suffix, // "B.sc
            viewModel.suffix.value
        )
    }

However, I get the following error when I run the test:

junit.framework.ComparisonFailure: expected:<[Mr]> but was:<[]>
at junit.framework.Assert.assertEquals(Assert.java:85)
at junit.framework.Assert.assertEquals(Assert.java:91)

How would I go about testing a MutableStateFlow/StateFlow like this? It seems that my test is not waiting long enough to have the correct data be emitted from the StateFlow.

Thanks

JamieRhys
  • 206
  • 6
  • 24

1 Answers1

0

It seems like you're trying to test both the Composable and the ViewModel at the same time while you need a different set of tests for each.

If for instance you have a button that displays a "Hello, $name" and saves the name to the database you test in an Instrumented test that the UI is changed and in a Unit test the database

So, the ViewModel (which has all the business logic) needs a Unit test. Yours is fairly simple, but tests never hurt :)

@Test
fun testViewModel_success(): Unit {
    val viewModel = UnitTestViewModel()

    // Assert that everything is set to their initial value
    assertEquals(viewModel.prefix.value, "")
    assertEquals(viewModel.firstName.value, "")
    assertEquals(viewModel.middleName.value, "")
    assertEquals(viewModel.lastName.value, "")
    assertEquals(viewModel.suffix.value, "")

    // call the function to set the values
    viewModel.saveContact(TestData.prefix, TestData.first, TestData.middle, TestData.last, TestData.suffix)

    // Assert stateFlows have the new values
    assertEquals(viewModel.prefix.value, TestData.prefix)
    assertEquals(viewModel.firstName.value, TestData.first)
    assertEquals(viewModel.middleName.value, TestData.middle)
    assertEquals(viewModel.lastName.value, TestData.last)
    assertEquals(viewModel.suffix.value, TestData.suffix)
}

Now, for the Composables you need an Instrumented test like the one you have. Instrumented tests are only to check if the data shown on the screen are correct. In your case, if you show your user data from the ViewModel's StateFlows somewhere on the UI after the button is clicked, you can check that your UI is updating correctly by calling the .assertTextEquals() or .assertTextContains(). If not you can only test if the UI changes appropriately (eg loading indicators, clearing up the TextFields etc)

Eliza Camber
  • 1,536
  • 1
  • 13
  • 21