0

Fairly standard Android tests that once executed normally, fail to build now that I have upgraded my Jetpack Compose-based app to Material3.

They all give me the same error at build time:

java.lang.IllegalStateException: blahblah.ui.MainActivity@b265e73 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

The error is thrown by:

at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl.setContent(ComposeUiTest.android.kt:452)`
at androidx.compose.ui.test.junit4.AndroidComposeTestRule.setContent(AndroidComposeTestRule.android.kt:200)
at blahblah.feature_memo_gallery.presentation.memo_gallery.MemoGalleryScreenTest.setUp(MemoGalleryScreenTest.kt:45)

Here is a sample of a test that used to build and run properly, but doesn't build anymore.

import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.remember
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import blahblah.core.di.AppModule
import blahblah.feature_memo_gallery.presentation.memo.MemoScreen
import blahblah.ui.MainActivity
import blahblah.ui.navigation.Screen
import blahblah.ui.theme.ScriblettTheme
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@HiltAndroidTest
@UninstallModules(AppModule::class)
class MemoGalleryScreenTest {

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @OptIn(ExperimentalMaterial3Api::class)
    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<MainActivity>()

    @OptIn(ExperimentalMaterial3Api::class)
    @ExperimentalAnimationApi
    @Before
    fun setUp() {
        hiltRule.inject()
        composeRule.setContent {  <<<<========= THIS LINE THROWS THE ERROR
            val navController = rememberNavController()
            val snackbarHostState = remember { SnackbarHostState() }
            ScriblettTheme {
                NavHost(
                    navController = navController,
                    startDestination = Screen.MemoScreen.route
                ) {
                    composable(route = Screen.MemoScreen.route) {
                        MemoScreen(navController = navController, snackbarHostState)
                    }
                }
            }
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Test
    fun clickSortButton_sortIsVisible() {
        composeRule.onNodeWithTag(TestTags.SORT).assertDoesNotExist()
        composeRule.onNodeWithContentDescription("Sort").performClick()
        composeRule.onNodeWithTag(TestTags.SORT).assertIsDisplayed()
    }
}

My problem is, I don't really understand the error message. I suspect it's Material3 related, because it's the thing I changed to cause the error, but I don't really have the foggiest.

John
  • 5,581
  • 6
  • 29
  • 46
  • I find that if I go to the MainActivity called in `val composeRule = createAndroidComposeRule()` and comment out the setContent block, tests run properly. So there can be only one setContent. So either I'm supposed to set up a dummy, empty MainActivity for testing purposes, or write a test that doesn't set its own content, and yet navigates to the screen to be tested? – John Aug 02 '22 at 05:26

2 Answers2

1

You need to set the content on the Activity:

composeRule.activity.setContent {
    val navController = rememberNavController()
    ...
}
per_jansson
  • 2,103
  • 4
  • 29
  • 46
0

You should use val composeRule = createComposeRule() and then use 'setContent()' on this. This will create an empty/shell activity to set your content in. Make sure that you add debugImplementation("androidx.compose.ui:ui-test-manifest:version") to gradle.

mars8
  • 770
  • 1
  • 11
  • 25