Configuring Test Dependencies — Get the Gradle Matrix Right
Compose UI tests fail at the dependency layer in three predictable ways: the JUnit4 surface is on the wrong source set, the ui-test-manifest artifact is missing or on the wrong configuration, or the host-test trio is incomplete. This skill encodes the exact matrix and the lint warning that polices it. Subsequent skills (choosing-test-rule-vs-runtest/SKILL.md, setting-up-host-vs-device-tests/SKILL.md) assume the dependencies are correct.
When to use this skill
- The user reports
java.lang.RuntimeException: Could not launch activity within 45 seconds. Examined activities: ... androidx.activity.ComponentActivityorActivityNotFoundException. - The IDE flags
createComposeRuleas unresolved orimport androidx.compose.ui.test.junit4.createComposeRuleas red. - Lint surfaces
TestManifestGradleConfiguration(severity WARNING) on abuild.gradle.ktsline. - The user wants to add Robolectric / host (JVM) tests for Compose and asks which artifacts to depend on.
- The user wants to opt into
enableAccessibilityChecks(...)and is unsure which artifact owns it.
When NOT to use this skill
- Dependencies are already correct and the user is choosing between
createComposeRule()andrunComposeUiTest { }— use./choosing-test-rule-vs-runtest/SKILL.md. - Dependencies compile but the test runs in the wrong source set (
test/vsandroidTest/) — use./setting-up-host-vs-device-tests/SKILL.md. - The test compiles and runs but is flaky/timing-out — start with
../../synchronization/synchronizing-with-idle/SKILL.md.
Prerequisites
- Android Gradle Plugin with the
com.android.applicationorcom.android.libraryplugin applied. - A Compose-enabled module (the
org.jetbrains.kotlin.plugin.composeplugin orbuildFeatures.compose = true). - A JUnit4 dependency on the test classpath (Compose's
ui-test-junit4artifact still uses JUnit4; JUnit5 requires a third-party Vintage engine bridge and is out of scope).
Workflow
1. Decide which test surfaces the module needs. A typical Compose feature module wants three: instrumentation tests for screenshot/RenderThread/full-stack coverage, host (Robolectric) tests for fast logic checks, and the manifest helper for
createComposeRule(). Pick from the matrix in step 2.2. Add dependencies on the correct configurations. Copy this into the module's
build.gradle.kts:
dependencies {
// --- Instrumentation tests (src/androidTest/) ---
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
// MUST be debugImplementation (see Pattern: WRONG vs RIGHT for ui-test-manifest below)
debugImplementation("androidx.compose.ui:ui-test-manifest")
// --- Host tests with Robolectric (src/test/) ---
testImplementation("androidx.compose.ui:ui-test") // common test API
testImplementation("androidx.compose.ui:ui-test-junit4") // ComposeTestRule + createComposeRule
testImplementation("androidx.compose.ui:ui-test-manifest") // ComponentActivity manifest entry
testImplementation("org.robolectric:robolectric:4.13") // pin a version
// --- Optional: accessibility validation (API 34+) ---
androidTestImplementation("androidx.compose.ui:ui-test-junit4-accessibility")
// Or, when using runComposeUiTest { } (no Rule):
androidTestImplementation("androidx.compose.ui:ui-test-accessibility")
}
The exact GA versions come from the Compose UI BOM or the developer's chosen composeVersion. Pin Robolectric independently — it does not ship in the Compose BOM.
- 3. If the test fails with
ActivityNotFoundExceptionforandroidx.activity.ComponentActivity, the manifest artifact is the cause 99 percent of the time. That artifact is a single-file library that merges this<activity>entry into the test APK manifest, taken fromcompose/ui/ui-test-manifest/src/main/AndroidManifest.xml:
<activity android:theme="@android:style/Theme.Material.Light.NoActionBar"
android:name="androidx.activity.ComponentActivity" android:exported="true" />
Without that entry, createComposeRule() and runComposeUiTest { } (which both default to launching androidx.activity.ComponentActivity via ActivityScenario.launch) crash at launch time. Diagnosis:
Confirm
ui-test-manifestis in the dependency list at all.Confirm it is on
debugImplementation(ortestImplementationfor host tests). If it is onandroidTestImplementation, lint flags it and Gradle still wires it in — but the AGP variant model intentionally separatesdebugandandroidTestmanifest merging, and putting it onandroidTestImplementationdoes NOT merge the<activity>into the host APK that the instrumentation runs against. The lint check exists precisely to catch this.4. Address the
TestManifestGradleConfigurationlint warning by moving the dependency todebugImplementation. The lint check lives incompose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/GradleDebugConfigurationDetector.kt. Severity isSeverity.WARNINGand the quick fix replaces the configuration name withdebugImplementation. The check fires when the artifact appears onimplementation,api,compileOnly,runtimeOnly,androidTestImplementation,testImplementation, or any custom configuration whose name starts withandroid/test/ one of the supported config names.5. Decide whether the manifest artifact is needed at all. It is required when:
- The test uses
createComposeRule()(no type parameter — defaults toandroidx.activity.ComponentActivity). - The test uses
runComposeUiTest { }(no type parameter — same default).
It is NOT required when:
- The test uses
createAndroidComposeRule<MyActivity>()AND the developer declares<activity android:name=".MyActivity" .../>themselves insrc/androidTest/AndroidManifest.xml(orsrc/debug/AndroidManifest.xml). - The test uses
createEmptyComposeRule()and launches its ownActivityScenarioagainst an Activity already declared in the production manifest.
- The test uses
6. For host tests, ensure all three artifacts are on
testImplementation. Robolectric drives the JVM Looper, but Compose's idling/clock implementations requireui-test(common) plusui-test-junit4(ComposeTestRuleinterface andcreateComposeRuleactual). Withoutui-test-manifestontestImplementation, Robolectric throws the sameActivityNotFoundExceptionbecause it parses the same manifest. The host source set in androidx itself uses this configuration — seecompose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt.7. Add accessibility artifacts only if the developer plans to call
enableAccessibilityChecks. They are independent of the JUnit4 surface:ui-test-accessibilityextendsComposeUiTest(the receiver ofrunComposeUiTest { }).ui-test-junit4-accessibilityextendsComposeTestRule/AndroidComposeTestRule. Both are@RequiresApi(34), both are no-op on Robolectric (Build.FINGERPRINT == "robolectric"triggers a logged warning) — see the accessibility skill in this repo for follow-up.
8. Sync Gradle and re-run the test. A clean
./gradlew :app:connectedDebugAndroidTest(instrumentation) or./gradlew :app:testDebugUnitTest(host) should compile and locateComponentActivity.
Patterns
Pattern: WRONG vs RIGHT — ui-test-manifest configuration
// WRONG
dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.compose.ui:ui-test-manifest")
}
// WRONG because: lint emits TestManifestGradleConfiguration (WARNING) and the manifest
// merger does not pull the <activity> entry into the merged APK manifest from the
// androidTest source set. createComposeRule() crashes with ActivityNotFoundException.
// RIGHT
dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
The lint check explicitly allows debugImplementation and warns on every other configuration. From GradleDebugConfigurationDetector.kt:
"The androidx.compose.ui:ui-test-manifest dependency is needed for launching a
Compose host, such as with createComposeRule. However, it only needs to be present
in testing configurations therefore use this dependency with the debugImplementation
configuration"
Pattern: WRONG vs RIGHT — host-test dependencies
// WRONG
dependencies {
testImplementation("androidx.compose.ui:ui-test-junit4")
testImplementation("org.robolectric:robolectric:4.13")
// Missing ui-test-manifest -> ActivityNotFoundException at runComposeUiTest launch
// Missing ui-test -> linker errors on common test API symbols
}
// RIGHT
dependencies {
testImplementation("androidx.compose.ui:ui-test")
testImplementation("androidx.compose.ui:ui-test-junit4")
testImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation("org.robolectric:robolectric:4.13")
}
Pattern: WRONG vs RIGHT — custom Activity without its own manifest entry
// WRONG
@get:Rule val rule = createAndroidComposeRule<MyHostActivity>()
// build.gradle.kts has only debugImplementation("androidx.compose.ui:ui-test-manifest")
// and src/androidTest/AndroidManifest.xml does NOT declare MyHostActivity.
// WRONG because: ui-test-manifest only declares ComponentActivity. Custom activities
// must be declared in the test APK manifest by the developer. Result: ActivityNotFoundException.
<!-- RIGHT — src/androidTest/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".MyHostActivity" android:exported="true" />
</application>
</manifest>
Pattern: complete sample build.gradle.kts
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
android {
namespace = "com.example.feature"
compileSdk = 35
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures { compose = true }
testOptions {
unitTests.isIncludeAndroidResources = true // required for Robolectric + Compose
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2025.06.00")
implementation(composeBom)
androidTestImplementation(composeBom)
testImplementation(composeBom)
// Production
implementation("androidx.compose.ui:ui")
implementation("androidx.activity:activity-compose")
// Instrumentation
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
// Host (Robolectric)
testImplementation("androidx.compose.ui:ui-test")
testImplementation("androidx.compose.ui:ui-test-junit4")
testImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation("org.robolectric:robolectric:4.13")
testImplementation("junit:junit:4.13.2")
}
testOptions.unitTests.isIncludeAndroidResources = true is required so Robolectric can read merged resources during host tests of Compose content.
Mandatory rules
- MUST put
androidx.compose.ui:ui-test-manifestondebugImplementationfor instrumentation tests, and ontestImplementationfor host tests. Never onandroidTestImplementation,implementation, orapi. - MUST keep
ui-test-junit4on the same source set as the test that importscreateComposeRule/ComposeTestRule. MixingandroidTestImplementation(instrumentation) andtestImplementation(host) in the same module is fine; mixing them on the wrong test class is a compile error. - MUST declare any non-
ComponentActivitytest host (custom Activity) in the test APK manifest atsrc/androidTest/AndroidManifest.xmlorsrc/debug/AndroidManifest.xml. Theui-test-manifestartifact only declaresandroidx.activity.ComponentActivity. - MUST NOT remove the
TestManifestGradleConfigurationlint check vialintOptions { disable("TestManifestGradleConfiguration") }. Apply the quick fix instead — the check exists to prevent silent test failures. - MUST NOT put
ui-test-manifeston a release configuration (releaseImplementation,implementation). The artifact is purely a test scaffold and bloats production APKs with an unwanted<activity>entry. - PREFERRED: rely on the Compose BOM (
androidx.compose:compose-bom) so allui-test-*artifacts share a coherent version. Robolectric is independent and pins separately. - PREFERRED: when in doubt about which artifact owns a symbol, search
androidx/compose/ui/ui-test*paths from the corpus rather than guessing — see References.
Verification
-
./gradlew :<module>:connectedDebugAndroidTestcompiles and runs withoutActivityNotFoundExceptionforandroidx.activity.ComponentActivity. -
./gradlew :<module>:lintdoes NOT reportTestManifestGradleConfiguration. -
./gradlew :<module>:testDebugUnitTestcompiles when host tests userunComposeUiTest { }orcreateComposeRule(). - The dependency block visibly separates
androidTestImplementation(instrumentation),debugImplementation(manifest),testImplementation(host trio). - If the developer added accessibility artifacts,
enableAccessibilityChecks(...)resolves at compile time inside an instrumentation test. - The test APK manifest contains exactly one
<activity android:name="androidx.activity.ComponentActivity"/>entry (run./gradlew :app:processDebugAndroidTestManifestand inspectbuild/intermediates/.../AndroidManifest.xml).
References
- Compose testing setup (Android Developers): https://developer.android.com/develop/ui/compose/testing#setup
- Compose testing overview: https://developer.android.com/develop/ui/compose/testing
- Compose UI release notes: https://developer.android.com/jetpack/androidx/releases/compose-ui
- Compose Multiplatform testing: https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html
compose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/GradleDebugConfigurationDetector.kt—TestManifestGradleConfigurationissue definition (severity WARNING, quick-fix todebugImplementation).compose/ui/ui-test-manifest/src/main/AndroidManifest.xml— the merged<activity android:name="androidx.activity.ComponentActivity">entry thatcreateComposeRule()depends on.compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt—runComposeUiTestlaunchesComponentActivity::class.javaby default (lines 184-197).compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt— canonical host-test class skeleton with Robolectric (@RunWith(AndroidJUnit4::class) @Config(minSdk = RobolectricMinSdk)).compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/Constants.kt—internal const val RobolectricMinSdk = 23.