Test error:MyActivity has already set content. If you have populated the Activity with a ComposeView, make sure to call setContent on that ComposeView

Viewed 259

When running Robolectric unit tests with the latest version of Compose 1.2.0, then the tests using createAndroidComposeRule fail with the following error:

MyActivity has already set content. If you have populated the Activity with a ComposeView, make sure to call setContent on that ComposeView instead of on the test rule; and make sure that that call to setContent {} is done after the ComposeTestRule has run

Code from one of the failing tests:

composeTestRule.setContent {
    Column {
        Text(textTitle)
        DemoScopedInjectedViewModelComposable()
    }
}
3 Answers

Compose with 1.2 has forbidden to overwrite the content of the activity rule. But this can be still done with setting content directly on activity, not activity test rule.

The easiest way to solve this issue is to set content not on the activity rule but on the activity itself. It can be done like this:

composeTestRule.activity.runOnUiThread {
    composeTestRule.activity.setContent {
        Column {
            Text(textTitle)
        }
    }
}

To simplify usage you can use following extension

fun <R : TestRule, A : ComponentActivity> AndroidComposeTestRule<R, A>.setContentOnActivity(
    content: @Composable () -> Unit
) {
    this.activity.runOnUiThread {
        this.activity.setContent {
            content()
        }
    }
}

Looking carefully, the error message helps a lot, even though it's talking about a ComposeView instead of an Activity. But according to it:

composeTestRule.setContent { ... }

should be changed to:

composeTestRule.activity.setContent { ... }

and the tests should run without this error occurring anymore.

The solution is to fetch the Compose View from the Activity (the Activity is available in the test rule), and then call setContent on that View instead of directly on the test rule, as the error message indicates.

Here is a test helper function I created to avoid this problem in my tests:

fun AndroidComposeTestRule<ActivityScenarioRule<MyActivity>, MyActivity>.clearAndSetContent(content: @Composable () -> Unit) {
    (this.activity.findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0) as? ComposeView)?.setContent(content)
        ?: this.setContent(content)
}

Updated test:

composeTestRule.clearAndSetContent {
    Column {
        Text(textTitle)
        DemoScopedInjectedViewModelComposable()
    }
}

For reference this is my test rule:

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