Nighty2010
Nighty2010

Reputation: 41

How to test rememberSaveable with Rotation Change in Jetpack Compose

I am using Android Studio Arctic fox and I try to test the behaviour of a variable that is stored using rememberSaveable. I did the test manualy by doing "Rotate Left" in Emulator. There the behaviour is as expected : value stored using remember is gone. value stored using rememberSaveable is still available. Now I want to get rid of manually testing and create an Instrumented test. But there I don't get the expected result. In the test both values are displayed in my output-Textfield. I tried to get right behaviour by adding waitForIdleSync() but it did not help.

I think it could have to do with "setting" the orientation. Maybe there is a method that is called by system when the orientation changes that I miss in my test case. But I have no clue what method I miss and how to call that method in my test.

@RunWith(AndroidJUnit4::class)
class SavingTurnTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Before
    fun setUp(){
        composeTestRule.setContent{
            MyApplicationTheme {
                SampleRemember_saveable()
            }
        }
        composeTestRule.onNodeWithTag("remember").performTextInput("Text_Remember")
        composeTestRule.onNodeWithTag("saveable").performTextInput("Text_Saveable")
        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
    }

    @Test
    fun saveTextOnTurn(){
        uiAutomation.setRotation(ROTATION_FREEZE_0)
        uiAutomation.setRotation(ROTATION_UNFREEZE)
        uiAutomation.setRotation(ROTATION_FREEZE_90)

        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
        
        // The next Line should fail, but test passes
        // composeTestRule.onNodeWithTag("output").assertTextContains("Text_Remember")
        
        // This test is ok
        composeTestRule.onNodeWithTag("output").assertTextContains("Text_Saveable")
        
        // The next Line should pass, but test fails
        composeTestRule.onNodeWithTag("output").assertTextEquals("Text_Saveable")
    }
}

Upvotes: 4

Views: 1584

Answers (1)

galactic.fungus
galactic.fungus

Reputation: 486

the below code will test a saveable but there are some caveats, first make sure that you add the ComponentActivity class to your AndroidManifest.xml file or replace it with your own activity class.

<activity android:name="androidx.activity.ComponentActivity" />

Second, it appears that the default option of using the line number for the key of a rememberSaveable doesn't work (At least I couldn't get it to work). However a custom key seems to work fine.

The basic idea is that we use createEmptyComposableRule to create a rule that is not attached to a specific Activity, then we create one with ActivityScenario. After setting content and making changes we use recreate() to restart the activity. Then you set your content once again, and check that the saveable was restored. There may be better ways of doing this as I am new to android development.

@RunWith(AndroidJUnit4::class)
class ExampleTestForSaveables {

    @get:Rule
    val composeTestRule = createEmptyComposeRule()

    private lateinit var scenario: ActivityScenario<ComponentActivity>

    @Before
    fun setup() {
        scenario = ActivityScenario.launch(ComponentActivity::class.java)
    }

    @Test
    fun composeWithFontSizeTest() {
        @Composable
        fun SomeTextFields() {
            var text1 by remember { mutableStateOf("") }
            // By default rememberSavable uses it's line number as its key, this doesn't seem 
            // to work when testing, instead pass a key
            var savableText: String by rememberSaveable(key = "savableText") {
                mutableStateOf("")
            }
            Column() {
                TextField(modifier = Modifier.testTag("remember"),
                    value = text1,
                    onValueChange = {text1 = it},
                    label = {})
                TextField(modifier = Modifier.testTag("saveable"),
                    value = savableText,
                    onValueChange = { savableText = it },
                    label = {  }
                )
            }
        }
        scenario.onActivity { activity ->
            activity.setContent {
                SomeTextFields()
            }
        }
        composeTestRule.onNodeWithTag("remember").performTextInput("Text_Remember")
        composeTestRule.onNodeWithTag("saveable").performTextInput("Text_Saveable")
        // Check that our input worked
        composeTestRule.onNodeWithTag("remember").assertTextContains("Text_Remember")
        composeTestRule.onNodeWithTag("saveable").assertTextContains("Text_Saveable")
        // Restart the activity
        scenario.recreate()
        // Compose our content
        scenario.onActivity { activity ->
            activity.setContent {
                SomeTextFields()
            }
        }
        // Was the text saved
        composeTestRule.onNodeWithTag("remember").assertTextContains("")
        composeTestRule.onNodeWithTag("saveable").assertTextContains("Text_Saveable")
    }
}

Upvotes: 1

Related Questions