A comprehensive testing toolkit for the Google Maps SDK for Android. Run fast, deterministic unit tests on your local JVM and execute pixel-perfect, offline golden image screenshot tests without physical devices or emulators.
This repository provides modular testing libraries tailored for different testing needs:
| Module | Description | Guide & Documentation |
|---|---|---|
| :robolectric-testing | Robolectric Shadows & JVM Utilities High-fidelity shadows for MapView, GoogleMap, SupportMapFragment, markers, and shapes. Features visual canvas drawing, lifecycle simulation, 1-liner KTX test harness, Kotlin Power-Assert helpers, and Google Truth domain subjects. |
Module README |
| :golden-testing | Deterministic Golden Image Testing Offline screenshot and visual regression testing using the real Maps SDK with synthetic, deterministic raster tiles ( MAP_TYPE_NONE), LRU caching, and pixel diff assertions. |
Module README |
| :visual-testing | AI Visual Testing & Golden Fallback Multimodal AI visual validation powered by Gemini, UiAutomator integration, dynamic Flash model resolution, and hybrid golden pixel diff with AI semantic fallback. |
Module README |
| :app | Sample Application Interactive demonstration app showcasing real-world usage of Google Maps SDK and testing integration. |
App Directory |
What are you testing?
|
+----------------------------------+----------------------------------+
| |
Behavioral / Functional UI / Rendering
| |
Do you need fast, headless Do you need screenshot or visual
JVM execution without devices? fidelity assertions?
| |
v +-----------------------+-----------------------+
[:robolectric-testing] | |
- Verify camera moves & bounds Deterministic Pixels AI Semantic / Resilient
- Test marker clicks & dragging | |
- Validate polygon geometry v v
- Assert with Power-Assert or Truth [:golden-testing] [:visual-testing]
- 100% offline & zero quota - Natural language assertions
- Deterministic synthetic tiles - Dynamic Flash model caching
- Strict pixel diff assertions - Hybrid golden fallback
- UiAutomator AI actions
| Capability | :robolectric-testing |
:golden-testing |
:visual-testing |
|---|---|---|---|
| Execution Environment | Local JVM (Headless) | Local JVM (RNG) & Android Device/Emulator | Android Device/Emulator & Connected CI |
| Network Required | ❌ No | ❌ No (Synthetic Tiles) | |
| Maps SDK API Quota | ❌ None | ❌ None | ❌ None (or standard for live map) |
| Camera & Lifecycle Testing | ✅ Yes | ✅ Yes | ✅ Yes |
| User Interaction Events | ✅ Yes (Clicks, Drags, Info Windows, User Gestures) | ✅ Yes (UiAutomator AI Actions) | |
| Geodesic & Domain Assertions | ✅ Yes (Kotlin Power-Assert & Google Truth) | ❌ N/A | ❌ N/A |
| Deterministic Golden Snapshots | ✅ Yes (2D Canvas + TileOverlay Rendering) |
✅ Yes (Mercator Grid / Solid / Pattern) | ✅ Yes (via :golden-testing) |
| Visual Diff Engine | ❌ N/A | ✅ Yes (Pixel comparison, baseline recording & _diff.png artifacts) |
✅ Yes (Hybrid pixel diff + AI fallback) |
| Multimodal AI Evaluation | ❌ N/A | ❌ N/A | ✅ Yes (Gemini Flash multimodal) |
| Dynamic Model Discovery | ❌ N/A | ❌ N/A | ✅ Yes (Latest stable Flash cached) |
| Jetpack Compose Integration | ✅ Yes | ✅ Yes (rememberGoldenTileProvider()) |
✅ Yes |
Add the desired artifacts to your gradle/libs.versions.toml:
[versions]
mapsTesting = "1.0.0-rc01"
robolectric = "4.17"
truth = "1.4.5"
[libraries]
maps-testing-robolectric = { module = "com.google.maps.android:maps-testing-robolectric", version.ref = "mapsTesting" }
maps-testing-golden = { module = "com.google.maps.android:maps-testing-golden", version.ref = "mapsTesting" }
maps-testing-visual = { module = "com.google.maps.android:maps-testing-visual", version.ref = "mapsTesting" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }Then reference them in your module's build.gradle.kts:
:robolectric-testing supports both Kotlin Power-Assert (zero extra library dependencies) and Google Truth domain subjects:
dependencies {
testImplementation(libs.maps.testing.robolectric)
testImplementation(libs.robolectric)
// Optional: include Google Truth if using fluent Truth subjects (MapsTruth.assertThat / LatLngSubject)
// instead of Kotlin Power-Assert (assertMap / assertMarker / assertGeo)
testImplementation(libs.truth)
}👉 See the :robolectric-testing README for Kotlin Power-Assert compiler plugin configuration, Google Truth setup, shadow reference tables, and testing recipes.
dependencies {
testImplementation(libs.maps.testing.golden)
}👉 See the :golden-testing README for synthetic tile strategies, visual galleries, and screenshot assertion recipes.
dependencies {
androidTestImplementation(libs.maps.testing.visual)
}👉 See the :visual-testing README for Gemini multimodal setup, hybrid golden fallback recipes, and UiAutomator AI actions.
Using Direct Dependency Coordinates (without Version Catalog)
dependencies {
// 1. Robolectric Shadows
testImplementation("com.google.maps.android:maps-testing-robolectric:1.0.0-rc01")
testImplementation("org.robolectric:robolectric:4.17")
// 2. Golden Image Testing
testImplementation("com.google.maps.android:maps-testing-golden:1.0.0-rc01")
// 3. AI Visual Testing
androidTestImplementation("com.google.maps.android:maps-testing-visual:1.0.0-rc01")
}Execute deterministic unit tests verifying map state, marker clicks, camera coordinates, and pixel drawing without an emulator using the 1-liner withTestMap { ... } harness:
// [START maps_robolectric_demo_test]
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34])
@EnableMapsShadows
class MapDemoTest {
@Test
fun testMapOverlaysAndAssertions() = withTestMap(width = 200, height = 200) {
// 1. Center the camera on Boulder, Colorado
val boulder = LatLng(40.0150, -105.2705)
moveCamera(CameraUpdateFactory.newLatLngZoom(boulder, 14f))
// 2. Add a Marker with a tinted icon
addMarker(
MarkerOptions()
.position(boulder)
.title("Boulder, CO")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)),
)
// 3. Draw a Polygon around the area
val northBoulderPark = LatLng(40.0185, -105.2705)
addPolygon(
PolygonOptions()
.add(
LatLng(40.0100, -105.2800),
LatLng(40.0100, -105.2600),
LatLng(40.0200, -105.2600),
LatLng(40.0200, -105.2800),
)
.fillColor(Color.GREEN)
.strokeColor(Color.DKGRAY)
.strokeWidth(2f),
)
// 4. Simulate a user tapping the marker (opens InfoWindow by default)
clickMarker("Boulder, CO")
// 5. Assert geodesic camera position, active InfoWindow, and rendered canvas pixel
assertThat(this)
.isCenteredOn(boulder, tolerance = 5.meters)
.hasZoom(14f)
.hasActiveInfoWindowWithTitle("Boulder, CO")
.hasPixelColorAt(northBoulderPark, Color.GREEN)
}
}
// [END maps_robolectric_demo_test]Locate a hosted MapView inside any Activity or Jetpack Compose view hierarchy with requireMapView():
// [START robolectric_view_hierarchy_lookup]
// Locate the hosted MapView inside an Activity or Jetpack Compose view hierarchy
val mapView = activity.window.decorView.requireMapView()
val map = mapView.getMapBlocking()
val seattle = LatLng(47.6062, -122.3321)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(seattle, 13f))
GoogleMapSubject.assertThat(map)
.isCenteredOn(seattle, tolerance = 5.meters)
.hasZoom(13f)
// [END robolectric_view_hierarchy_lookup]And inject deterministic synthetic tile providers directly into Jetpack Compose GoogleMap composables:
// [START maps_golden_compose_extensions]
/**
* Remembers a [GoldenTileProvider] instance across recompositions with the specified [strategy].
*
* Suitable for use with Maps Compose `TileOverlay(tileProvider = ...)`:
* ```kotlin
* GoogleMap(
* properties = MapProperties(mapType = MapType.NONE),
* ) {
* val tileProvider = rememberGoldenTileProvider()
* TileOverlay(
* tileProvider = tileProvider,
* fadeIn = false,
* zIndex = -1000f,
* )
* }
* ```
*
* @param strategy Strategy generating synthetic tiles. Defaults to [GoldenTileStrategy.CoordinateGrid].
* @param tileSize Dimension in pixels for tile width and height. Defaults to 256.
* @return A remembered [GoldenTileProvider].
*/
@Composable
public fun rememberGoldenTileProvider(
strategy: GoldenTileStrategy = remember { GoldenTileStrategy.CoordinateGrid() },
tileSize: Int = 256,
): GoldenTileProvider = remember(strategy, tileSize) {
GoldenTileProvider(strategy = strategy, tileSize = tileSize)
}
// [END maps_golden_compose_extensions]Render maps with synthetic tiles and assert pixel-perfect fidelity across repeated captures and visual regressions:
// [START maps_sample_golden_map_test]
/**
* Sample test suite demonstrating deterministic golden screenshot testing with Google Maps.
*
* In this pattern:
* 1. The map is set to `MAP_TYPE_NONE` and backed by a deterministic synthetic tile provider.
* 2. Overlays (circles, polygons, markers) are added to the map.
* 3. The map snapshot is captured via [GoogleMap.captureSnapshot].
* 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches].
*/
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [34])
@EnableMapsShadows
public class SampleGoldenMapTest {
private lateinit var googleMap: GoogleMap
@Before
public fun setUp() {
googleMap = createTestMap(width = 200, height = 200)
// Enable deterministic golden tiles
googleMap.enableGoldenTesting(strategy = GoldenTileStrategy.SolidColorHash())
}
@Test
public fun sample_mapRenderingIsDeterministicAcrossRepeatedCaptures() = runTest {
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(37.7749, -122.4194), 10f))
googleMap.addCircle(
CircleOptions()
.center(LatLng(37.7749, -122.4194))
.radius(5000.0)
.fillColor(Color.RED)
.strokeWidth(0f),
)
// First capture (acts as baseline)
val baselineSnapshot = googleMap.captureSnapshot()
// Second capture under identical state
val secondSnapshot = googleMap.captureSnapshot()
// Should match exactly (0% pixel diff)
GoldenImageDiff.assertMatches(
expected = baselineSnapshot,
actual = secondSnapshot,
maxDiffPercentage = 0.0,
message = "Repeated map captures must be 100% pixel identical",
)
}
@Test
public fun sample_visualRegressionIsDetectedWhenOverlayColorChanges() = runTest {
val center = LatLng(0.0, 0.0)
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 5f))
// Baseline render with BLUE polygon
val polygon = checkNotNull(
googleMap.addPolygon(
PolygonOptions()
.add(LatLng(-5.0, -5.0), LatLng(-5.0, 5.0), LatLng(5.0, 5.0), LatLng(5.0, -5.0))
.fillColor(Color.BLUE)
.strokeWidth(0f),
),
)
val baselineSnapshot = googleMap.captureSnapshot()
// Introduce visual regression: change fill color to GREEN
polygon.fillColor = Color.GREEN
val modifiedSnapshot = googleMap.captureSnapshot()
// Verify that GoldenImageDiff flags the visual regression
val diff = GoldenImageDiff.compare(baselineSnapshot, modifiedSnapshot)
assertThat(diff.matches).isFalse()
assertThat(diff.diffPixelCount).isGreaterThan(0)
assertThrows(AssertionError::class.java) {
GoldenImageDiff.assertMatches(baselineSnapshot, modifiedSnapshot, maxDiffPercentage = 0.0)
}
}
// [END maps_sample_golden_map_test]Capture screen state and verify high-level layout, accessibility, and visual presentation with Gemini Vision:
// [START maps_visual_test_sample]
/**
* Sample instrumented visual test demonstrating AI-powered visual verification
* and hybrid golden testing with Gemini.
*/
@RunWith(AndroidJUnit4::class)
class VisualMapSampleTest : BaseVisualTest() {
@Test
fun verifyStoreLocatorMapRendering() = runBlocking {
// 1. Launch MainActivity
val intent = Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
context.startActivity(intent)
// 2. Wait for application window to settle
uiDevice.wait(Until.hasObject(By.pkg(context.packageName).depth(0)), 10000)
waitForMapRendering(timeoutSeconds = 5)
// 3. Capture screenshot of current map state
val screenshotBitmap = captureScreenshot("store_locator_screenshot.png")
// 4. Natural language prompt specifying expected visual elements
val prompt = """
Please act as a UI tester and analyze this screenshot of an Android application.
Verify the following criteria:
1. Confirm that the application displays a Google Map surface with the "Store Locator" header.
2. Confirm that store location pins or map tiles are visible on the screen.
3. Confirm that the application interface has rendered cleanly without error banners, blank crash dialogs, or ANRs.
If all criteria are met, reply with "PASSED".
If any criteria is not met, please detail the discrepancy.
""".trimIndent()
// 5. Hybrid verification: check local golden baseline first, fallback to Gemini Vision
verifyScreenshotWithGoldenFallback(
testName = "store_locator_initial_state",
screenshotBitmap = screenshotBitmap,
prompt = prompt,
passCondition = { it.contains("PASSED", ignoreCase = true) },
)
}
}
// [END maps_visual_test_sample]This repository ships with ready-to-use AI coding agent skills in .agents/skills/ (symlinked into .gemini/skills/ and .claude/skills/), grounded in region-tagged sample tests from this repo:
| Skill | Testing Paradigm | Path |
|---|---|---|
android-maps-testing |
Unified Toolkit & Decision Matrix — Master guide covering all 3 testing tiers, TDD/CUJ workflows, anti-bloat architecture, references, and starter templates. | .agents/skills/android-maps-testing/SKILL.md |
maps-robolectric-testing |
Paradigm 1: Headless JVM Unit Testing (:robolectric-testing) — withTestMap / createTestMap, Google Truth (assertThat(map)), Kotlin Power-Assert (assertMap), Java shadows, newLatLngBounds, simulateUserCameraMove, and requireMapView(). |
.agents/skills/maps-robolectric-testing/SKILL.md |
maps-golden-testing |
Paradigm 2: Deterministic Offline Golden Testing (:golden-testing) — Synthetic tile strategies (CoordinateGrid, SolidColorHash, Checkerboard), rememberGoldenTileProvider(), 1-liner assertMatchesGolden(), -Dmaps.golden.record=true, and _diff.png artifacts. |
.agents/skills/maps-golden-testing/SKILL.md |
maps-visual-testing |
Paradigm 3: Multimodal AI Visual Testing (:visual-testing) — BaseVisualTest, Gemini Flash dynamic model resolution, committed src/androidTest/assets/goldens/ fast path, and hybrid golden fallback. |
.agents/skills/maps-visual-testing/SKILL.md |
Comprehensive API documentation generated by Dokka is available online at: 👉 googlemaps.github.io/android-maps-testing
To generate the documentation locally:
./gradlew docs:dokkaGenerateThe generated HTML will be located at docs/build/dokka/html/index.html.
Contributions are welcome! Please check the following before submitting a pull request:
- Contributing Guidelines
- Code of Conduct
- Security Policy
- Code Owners
- Open an Issue using our templates for bug reports, feature requests, or questions.
Test coverage is tracked across merged commits on the coverage-history branch using Kotlinx Kover. See COVERAGE.md for the latest coverage reports and historical trends.
Copyright 2026 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.