Testing Setup
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Workflow
- Software development
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, step-by-step procedure for establishing a complete testing infrastructure in Android projects. It guides developers through analyzing existing dependencies, configuring dependency injection for testability, and implementing various testing layers including unit, UI, screenshot, and end-to-end tests. It emphasizes modern practices like Compose-based screenshot testing and provides clear paths for both legacy and current Android Gradle Plugin configurations.
What it does
- Analyzes current project dependencies and testing frameworks.
- Configures dependency injection frameworks for testable code.
- Sets up unit, UI, and end-to-end testing harnesses.
- Implements screenshot testing using Compose or device-based tools.
- Provides guidance on refactoring code for better testability.
- Documents the testing strategy and setup for the project.
When to use it
- When starting a new Android project and needing a testing foundation.
- When an existing project lacks a formal or comprehensive testing strategy.
- When migrating or upgrading screenshot testing configurations.
- When needing to improve test coverage for business logic or UI components.
Advantages
- Standardizes testing practices across Android modules.
- Supports both modern Compose and legacy View-based architectures.
- Provides clear migration paths for screenshot testing.
- Promotes testable code through dependency injection and fakes.
Usage examples
- “Analyze my current Android project and suggest a testing strategy.”
- The agent will examine your build files, identify existing testing dependencies, and generate a report outlining the recommended setup for unit, UI, and screenshot tests.
- “Set up screenshot testing for my Compose-based Android app.”
- The agent will configure the necessary screenshot engine, update your build files, and guide you through creating your first screenshot test suite.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/android/testing-setup/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip testing-setup.zip -d .agents/skills
Documentation by Google LLC
Step 1: Analyze the current testing setup
To understand the testing setup of an existing project, look for these dependencies in the libs.versions.toml file, or build files:
Dependency Injection framework used. For example, Hilt, Koin, Anvil, or vanilla Dagger.
Unit (local) testing framework this project uses. For example, JUnit4 or JUnit5.
Mocking framework (if any) used for unit tests, and for instrumented and UI tests. For example, Mockito or Mockk.
Robolectric. Identify which of these three uses apply:
Used in unit tests to have fakes for platform entities
To run behavior UI tests without a device or emulator. For example, used to run Espresso or Compose tests.
To do screenshot testing with Roborazzi
Is the app 100% Compose, Views or hybrid?
Behavior UI tests:
Compose Tests (
androidx.compose.ui:ui-test-*)Espresso Tests for Views. Check for wrappers such as Kaspresso. Dependencies:
androidx.test.espresso:espresso-core,androidx.test:runner,androidx.test:rules.
Screenshot tests. Identify which of these types the project uses:
Instrumented (device-based). For example, using Dropshots.
Based on Robolectric, so they run locally. For example, using Roborazzi.
Based on LayoutLib, so they run locally. For example, Paparazzi, or the Compose Preview Screenshot Testing tool.
End to end tests (also known as Release Candidate tests) always run on device and use high-level frameworks such as UIAutomator, Appium, or Robotium.
Generate a Markdown report with the analysis
Step 2: Set up Dependency Injection frameworks for testing
If there is no Dependency Injection framework, install one: if it's a multiplatform application, ask the user whether they want to install Koin, or kotlin-inject. If it's not multiplatform, install Hilt.
Install the testing dependencies (for example com.google.dagger:hilt-compiler
that should be applied with a kspAndroidTest configuration).
[!IMPORTANT] Important: Always consult the documentation of the applicable framework to learn about testing (for example: Hilt testing guide, Koin Instrumented tests).
For instrumented tests, create and configure (by adding
testInstrumentationRunner to the build gradle files) a new test runner and
apply the testing rules required by the framework (for example in Hilt, annotate
your test classes with @HiltAndroidTest and apply the HiltAndroidRule).
Other frameworks use other mechanisms, consult their documentation.
Step 3: Install frameworks
Unless otherwise specified, respect the current stack of testing frameworks.
If there are no testing frameworks, and the user didn't specify any preference, install the following:
JUnit4 for local and instrumented tests
JaCoCo for test coverage
For UI tests: if the project has views, Espresso. If it's fully Compose, use the Compose Testing APIs.
Robolectric to run UI Tests
Compose Preview Screenshot Testing tool for screenshot tests. Check the project's AGP version first, then pick the matching setup and follow it strictly:
AGP 9.5.0-alpha03 or higher (recommended): configure an AGP test suite using
testOptions { screenshotTests.create(...) }, as described in Screenshot testing with test suites.Lower AGP versions: use the legacy standalone plugin setup with
com.android.compose.screenshot, and tell the user it's deprecated. Don't upgrade AGP as part of setting up screenshot tests. Mention that test suites are available on 9.5.0-alpha03 or higher, and leave the upgrade as a separate decision.
Don't combine both setups in the same module. If the project already uses the standalone plugin and its AGP version supports test suites, offer to migrate it using the tasks and paths mapping table in Screenshot testing with test suites.
Dropshots for device screenshot tests
If a mocking framework is necessary, install Mockk (
io.mockk:mockk). Do not install it unless it is clearly necessary.
If instrumented screenshot tests are requested, install Dropshots.
If end-to-end testing is requested, install UI Automator.
Step 4: Refactor and create fakes for testing
Refactor for unit tests
In the next sections you'll be asked to create tests. If you have dependencies on Android framework classes, or entities that are not part of the codebase:
First, use a fake. If it doesn't exist, create an interface for the class and a "Default" implementation with the existing code. Add the Fake version to the test sourceset (test or androidTest).
If not possible to use a fake (example: no access to the class or interface), mock the dependencies.
Refactor for UI tests
If you need to fake components to make testing easier and faster and more reliable, replace slow and problematic dependencies with fakes. Use runtime fakes using the Dependency Injection framework installed to:
Simulate different scenarios with the user (wrong credentials or a password reset flow), with a server (no connection, server down, or bad JSON from the server) or with a platform component (insufficient permissions, no disk space, no front camera available)
Improve speed and reliability (replacing a database with an in-memory database, replacing a repository with an in-memory fake to avoid hitting the network)
Step 5: Unit testing
Create a task to add or review unit tests in every file that contains business logic (ViewModels, Repositories, and database-related classes such as DAOs). Don't create unit tests for Activities, Compose layouts, or dependency injection configuration files.
Step 6: UI testing
Espresso or Compose UI tests live in the test sourceset because they will be
run with Robolectric. If instrumented (emulator or device) tests are requested,
put them in the androidTest sourceset.
Step 7: Test databases
If the database is using SQLite (through a library such as Room or SQLDelight), create instrumented tests using an in-memory database to make sure that they work with the SQLite engine on device.
Step 8: Screenshot tests
Regardless of the framework used, screenshot tests focus on two types of tests:
Screen-level screenshot tests, where each screen is tested in nine different sizes, combining compact, medium and expanded widths (400, 610, 900 dp) and heights (400, 500 and 1000 dp).
Screen-level variations. Add a mobile (400x500) screenshot of:
All the alternative themes, if used.
Font scale set to 1.5.
Component-level screenshot tests, where each component is tested in different themes and font scales.
Behavior isn't tested with screenshots, but do test different common scenarios if their UIs change a lot depending on the state. For example, test loading screens by injecting a loading state to the UI or simulating it with a fake.
Step 9: UI Behavior tests
Test the UI logic using behavior tests, which ensures that the UIs react as expected when different states are passed, and when user actions are performed.
Compose UI behavior tests
Use the ComposeTestRule with a
ComponentActivityto access resources such as strings.Always match with semantic matchers first. If the matcher is too complicated to write (using more than three matchers to find a single element), use
testTag.Always verify state restoration.
Views (XML) UI behavior tests
Use Espresso to match views and interact with them.
Step 10: Navigation tests
Create a test suite to verify navigation logic. Include:
Back handling
Deeplinks
Special patterns like "exit through home" with multiple backstacks.
Step 11: Simulate different window sizes and settings
For Compose layouts, use DeviceConfigurationOverride described in "UI testing
common patterns" to simulate different window sizes, font scales
Step 12: End-to-end tests
Create a low number (about 5% of all tests) of end-to-end tests that cover big user journeys. Use Compose Test APIs or Espresso for that. If you have to access platform features such as notifications or system UI, use UI Automator.
If you need to take screenshots of the app running in a device, use Dropshots. You need a device for screenshot tests when verifying interaction with the system UI (examples: edge-to-edge rendering, notifications, picture-in-picture)
Step 13: Instrumented Screenshot tests
Install the com.dropbox.dropshots plugin in the module and a Dropshots()
JUnit Rule. Create a new instrumented screenshot test for one of the app's
features.
Step 14: Install JaCoCo
Install JaCoCo for local testing code coverage.
Add the
jacocoplugin to each module that contains tests.
Final touches
Ask whether to document the findings of the analysis and the changes applied to the testing strategy. If the user agrees:
If there is an AGENTS.md file present in the project, update it with any changes you've made to the testing strategy.
If there is no AGENTS.md file, create a new file (docs/testing.md) with a description of the testing strategy, including the commands needed to run every type of test and where the screenshot reference files live. Also create a new AGENTS.md file in the root and create a link to docs/testing.md.
Files
- ATTRIBUTION.mdAdded by AutoGineer952 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md10.0 KiB
- references/android/develop/ui/compose/testing/common-patterns.md14.9 KiB
- references/android/studio/preview/compose-screenshot-testing-with-testsuites.md7.3 KiB
- references/android/studio/preview/compose-screenshot-testing.md12.7 KiB
- references/android/training/dependency-injection/hilt-testing.md14.5 KiB
references/android/training/dependency-injection/hilt-testing.md
One of the benefits of using dependency injection frameworks like Hilt is that it makes testing your code easier.
Unit tests
Hilt isn't necessary for unit tests, since when testing a class that uses constructor injection, you don't need to use Hilt to instantiate that class. Instead, you can directly call a class constructor by passing in fake or mock dependencies, just as you would if the constructor weren't annotated:
@ActivityScoped
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { ... }
class AnalyticsAdapterTest {
@Test
fun `Happy path`() {
// You don't need Hilt to create an instance of AnalyticsAdapter.
// You can pass a fake or mock AnalyticsService.
val adapter = AnalyticsAdapter(fakeAnalyticsService)
assertEquals(...)
}
}The same applies to ViewModel classes obtained by calling hiltViewModel() in
your composables. In unit tests, construct the ViewModel directly with fakes.
For information on how state flows from a ViewModel into composables, see
State and Jetpack Compose and Where to hoist state.
End-to-end tests
For integration tests, Hilt injects dependencies as it would in your production code. Testing with Hilt requires no maintenance because Hilt automatically generates a new set of components for each test.
Adding testing dependencies
To use Hilt in your tests, include the hilt-android-testing dependency in your
project:
dependencies {
// For Robolectric tests.
testImplementation("com.google.dagger:hilt-android-testing:2.57.1")
kspTest("com.google.dagger:hilt-android-compiler:2.57.1")
// For instrumented tests.
androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1")
kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1")
// Compose UI test rule.
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}[!NOTE] Note: If you use Jetpack integrations (like
hilt-navigation-composeto obtain a ViewModel throughhiltViewModel()), you must also add their annotation processors to your test dependencies.
UI test setup
You must annotate any UI test that uses Hilt with @HiltAndroidTest. This
annotation is responsible for generating the Hilt components for each test.
Also, you need to add the HiltAndroidRule to the test class. It manages the
components' state and is used to perform injection on your test:
@HiltAndroidTest
class SettingsScreenTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeRule = createAndroidComposeRule<HiltTestActivity>()
// Compose UI tests here.
}[!NOTE] Note: If you have other rules in your test, see Multiple TestRule objects in your instrumented test.
Next, your test needs to know about the Application class that Hilt
automatically generates for you.
To let Hilt inject dependencies, you must create an empty activity named
HiltTestActivity in your androidTest source set and annotate it with
@AndroidEntryPoint. createAndroidComposeRule then uses this activity as the
host for your composable content.
Test application
You must execute instrumented tests that use Hilt in an Application object
that supports Hilt. The library provides HiltTestApplication for use in tests.
If your tests need a different base application, see Custom application for
tests.
You must set your test application to run in your instrumented tests or Robolectric tests. The following instructions aren't specific to Hilt, but are general guidelines on how to specify a custom application to run in tests.
Set the test application in instrumented tests
To use the Hilt test application in instrumented tests, you need to configure a new test runner. This makes Hilt work for all of the instrumented tests in your project. Perform the following steps:
Create a custom class that extends
AndroidJUnitRunnerin theandroidTestfolder.Override the
newApplicationfunction and pass in the name of the generated Hilt test application.
// A custom runner to set up the instrumented application class for tests.
class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}Next, configure this test runner in your Gradle file as described in the instrumented unit test guide. Make sure you use the full classpath:
android {
defaultConfig {
// Replace com.example.android.dagger with your class path.
testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
}
}Set the test application in Robolectric tests
If you use Robolectric to test your UI layer, you can specify which application
to use in the robolectric.properties file:
application = dagger.hilt.android.testing.HiltTestApplication
Alternatively, you can configure the application on each test individually by
using Robolectric's @Config annotation:
@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsScreenTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
// Robolectric tests here.
}Testing features
Once Hilt is ready to use in your tests, you can use several features to customize the testing process.
Inject types in tests
To inject types into a test, use @Inject for field injection. To tell Hilt to
populate the @Inject fields, call hiltRule.inject().
See the following example of an instrumented test:
@HiltAndroidTest
class SettingsScreenTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeRule = createAndroidComposeRule<HiltTestActivity>()
@Inject
lateinit var analyticsAdapter: AnalyticsAdapter
@Before
fun init() {
hiltRule.inject()
}
@Test
fun settingsScreen_showsTitle() {
composeRule.setContent {
SettingsScreen()
}
composeRule.onNodeWithText("Settings").assertIsDisplayed()
// analyticsRepository is available here.
}
}Replace a binding
If you need to inject a fake or mock instance of a dependency, you need to tell Hilt not to use the binding that it used in production code and to use a different one instead. To replace a binding, you need to replace the module that contains the binding with a test module that contains the bindings that you want to use in the test.
For example, suppose your production code declares a binding for
AnalyticsService as follows:
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}To replace the AnalyticsService binding in tests, create a new Hilt module in
the test or androidTest folder with the fake dependency and annotate it
with @TestInstallIn. All the tests in that folder are injected with the fake
dependency instead.
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [AnalyticsModule::class]
)
abstract class FakeAnalyticsModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
fakeAnalyticsService: FakeAnalyticsService
): AnalyticsService
}Because composables typically consume these dependencies indirectly through a
ViewModel obtained with hiltViewModel(), replacing the binding in Hilt is
enough. The composable under test picks up the fake automatically.
Replace a binding in a single test
To replace a binding in a single test instead of all tests, uninstall a Hilt
module from a test using the @UninstallModules annotation and create a new
test module inside the test.
Following the AnalyticsService example from the previous version, begin by
telling Hilt to ignore the production module by using the @UninstallModules
annotation in the test class:
@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest { ... }Next, you must replace the binding. Create a new module within the test class that defines the test binding:
@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {
@Module
@InstallIn(SingletonComponent::class)
abstract class TestModule {
@Singleton
@Binds
abstract fun bindAnalyticsService(
fakeAnalyticsService: FakeAnalyticsService
): AnalyticsService
}
// ...
}This only replaces the binding for a single test class. If you want to replace
the binding for all test classes, use the @TestInstallIn annotation from the
section above. Alternatively, you can put the test binding in the test module
for Robolectric tests, or in the androidTest module for instrumented tests.
The recommendation is to use @TestInstallIn whenever possible.
[!WARNING] Warning: You cannot uninstall modules that are not annotated with
@InstallIn. Attempting to do so causes a compilation error.
[!WARNING] Warning:
@UninstallModulescan only uninstall@InstallInmodules, not@TestInstallInmodules. Attempting to do so causes a compilation error.
[!NOTE] Note: As Hilt creates new components for tests that use
@UninstallModules, it can significantly impact unit test build times. Use it when necessary and prefer using@TestInstallInwhen the bindings need to be replaced in all test classes.
Binding new values
Use the @BindValue annotation to easily bind fields in your test into the Hilt
dependency graph. Annotate a field with @BindValue and it will be bound under
the declared field type with any qualifiers that are present for that field.
In the AnalyticsService example, you can replace AnalyticsService with a
fake by using @BindValue:
@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {
@BindValue @JvmField
val analyticsService: AnalyticsService = FakeAnalyticsService()
...
}This simplifies both replacing a binding and referencing a binding in your test by allowing you to do both at the same time.
@BindValue works with qualifiers and other testing annotations. For example,
if you use testing libraries such as
Mockito, you could use it in a
Robolectric test as follows:
...
class SettingsScreenTest {
...
@BindValue @ExampleQualifier @Mock
lateinit var qualifiedVariable: ExampleCustomType
// Robolectric tests here
}If you need to add a multibinding,
you can use the @BindValueIntoSet and @BindValueIntoMap annotations in place
of @BindValue. @BindValueIntoMap requires you to also annotate the field
with a map key annotation.
Special cases
Hilt also provides features to support nonstandard use cases.
Custom application for tests
If you cannot use HiltTestApplication because your test application needs to
extend another application, annotate a new class or interface with
@CustomTestApplication, passing in the value of the base class you want the
generated Hilt application to extend.
@CustomTestApplication will generate an Application class ready for testing
with Hilt that extends the application you passed as a parameter.
@CustomTestApplication(BaseApplication::class)
interface HiltTestApplicationIn the example, Hilt generates an Application named
HiltTestApplication_Application that extends the BaseApplication class. In
general, the name of the generated application is the name of the annotated
class appended with _Application. You must set the generated Hilt test
application to run in your instrumented tests or
Robolectric tests as described in Test
application.
[!NOTE] Note: Because
HiltTestApplication_Applicationis code that Hilt generates at runtime, the IDE might highlight it in red until you run your tests.
Multiple TestRule objects in your instrumented test
Compose UI tests already combine HiltAndroidRule with a Compose test rule
such as createAndroidComposeRule. If you have additional TestRule objects,
make sure HiltAndroidRule runs first. Declare the execution order with the
order attribute on @Rule:
@HiltAndroidTest
class SettingsScreenTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeRule = createAndroidComposeRule<HiltTestActivity>()
@get:Rule(order = 2)
val otherRule = SomeOtherRule()
// UI tests here.
}Alternatively, you can wrap the rules with RuleChain, placing
HiltAndroidRule as the outer rule.
@HiltAndroidTest
class SettingsScreenTest {
@get:Rule
var rule = RuleChain.outerRule(HiltAndroidRule(this)).
around(SettingsScreenTestRule(...))
// UI tests here.
}Use an entry point before the singleton component is available
The @EarlyEntryPoint annotation provides an escape hatch when a Hilt entry
point needs to be created before the singleton component is available in a
Hilt test.
More information about @EarlyEntryPoint in the
Hilt documentation.
Additional resources
To learn more about testing, see the following additional resources:
Documentation
Views content
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · testing/testing-setup · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download testing-setup.zip