How to Test Location Services on Android (Complete Guide)
Location‑aware features are now a core part of many Android apps: navigation, ride‑hail, social check‑ins, fitness tracking, geo‑fencing, and targeted advertising. When the location subsystem fails, u
Why Location Services Testing Matters
Location‑aware features are now a core part of many Android apps: navigation, ride‑hail, social check‑ins, fitness tracking, geo‑fencing, and targeted advertising. When the location subsystem fails, users see wrong maps, missed check‑ins, inaccurate workout stats, or even privacy leaks. From a business perspective, a bad location experience can lead to abandoned flows, negative reviews, and regulatory scrutiny if personal data is mishandled.
Testing location services is more than verifying that a latitude/longitude pair is returned. You must validate:
- Permission handling – runtime request, denial, and revocation.
- Provider selection – GPS, network, passive, and fused provider behavior under varying signal conditions.
- Mock location – how the app reacts when developer‑enabled mock locations are present (a common attack vector).
- Background execution – location updates while the app is in the background, Doze mode, and battery‑optimization restrictions.
- Battery impact – frequency of requests, use of PRIORITY_BALANCED_POWER_ACCURACY vs. PRIORITY_HIGH_ACCURACY, and effect on device standby.
- Accessibility – ensuring that location‑dependent UI is reachable via TalkBack and that error messages are announced.
- Security/privacy – preventing leakage of coarse location to third‑party libraries, verifying that location is not logged unintentionally, and confirming that the app respects the user’s choice to disable location.
If any of these areas is overlooked, the app may work in a controlled lab but fail when users move between indoor/outdoor environments, switch carriers, or enable battery‑saver modes. The following sections give a complete, practical guide to cover those risks.
---
Test Matrix for Location Services
The table below organizes tests by objective, description, expected outcome, and severity. Use it as a checklist when planning manual or automated suites.
| # | Test Objective | Description | Expected Result | Severity |
|---|---|---|---|---|
| 1 | Happy‑path GPS acquisition | Launch app with location permission granted, ensure clear sky view (or use emulator GPS fix). | Location callback returns a fix within 5 s, accuracy < 20 m, bearing updates on movement. | Critical |
| 2 | Permission denied flow | Deny location permission at runtime. | App shows a clear rationale, disables location‑dependent UI, does not crash. | High |
| 3 | Permission revoked while running | Grant permission, start location, then revoke via Settings while app is foreground. | Location updates stop, app handles onProviderDisabled gracefully, no ANR. | High |
| 4 | Mock location detection | Enable developer options → “Select mock location app”, install a mock‑location provider, feed false coordinates. | App either ignores mock location (if security‑sensitive) or shows a warning; never uses mock data for critical functions. | Medium |
| 5 | Network‑only provider | Disable GPS, enable only Wi‑Fi/cellular location. | App receives a coarse fix (accuracy ~ 500‑2000 m) within 10 s; UI reflects lower accuracy. | Medium |
| 6 | GPS signal loss simulation | Start with GPS fix, then execute adb shell cmd location setgpsdisabled 1 or use emulator “GPS off”. | Location callbacks stop, app shows stale‑data warning or falls back to network provider. | Medium |
| 7 | Background location limits | Start a foreground service that requests location, then press Home, enable battery optimization for the app. | Location updates continue if a foreground service is used; otherwise updates stop after the system‑imposed interval (≈ few minutes). | High |
| 8 | Doze mode impact | Leave device idle, unplugged, battery < 20 % to trigger Doze. Verify location request frequency. | Requests are deferred to maintenance windows; app does not miss critical geo‑fence transitions if using FusedLocationProviderClient with setPriority(PRIORITY_BALANCED_POWER_ACCURACY). | Low |
| 9 | Battery consumption measurement | Use adb shell dumpsys batterystats --reset, run location‑heavy scenario for 10 min, then adb shell dumpsys batterystats. | Compute mA drain; ensure it stays within defined budget (e.g., < 150 mA for continuous high‑accuracy GPS). | Low |
| 10 | Accessibility of location errors | Trigger a location error (e.g., deny permission, then try to start navigation). Activate TalkBack. | Error message is announced, focus moves to an actionable button (e.g., “Enable location”). | Medium |
| 11 | Security scan for location leakage | Run a static analysis tool (e.g., MobSF) or manually inspect logs for Log.d containing latitude/longitude. | No location data appears in plain‑text logs or crash reports. | High |
| 12 | Edge case: rapid provider switching | Toggle GPS on/off every 2 seconds while app requests updates. | App does not crash, does not flood the system with requests, respects minTime/minDistance parameters. | Medium |
| 13 | Edge case: simulated altitude | Use emulator GPS controls to set altitude to 0 m then 2000 m. | App correctly receives altitude field (if requested) and does not treat it as error. | Low |
| 14 | Edge case: time‑travel test | Set device clock far forward/backward while requesting location. | Location timestamps remain monotonic; app does not rely on system clock for expiry logic. | Low |
| 15 | Edge case: external SD‑card storage of mock GPX | Place a GPX file on external storage, load it via a third‑party mock app. | App handles malformed GPX gracefully (no crash). | Low |
How to use the matrix
- Prioritize Critical and High items for every release.
- Medium items can be covered in nightly automated runs.
- Low items are useful for quarterly deep‑dive or when adding new location‑heavy features.
---
Manual Testing Approach
Manual testing remains valuable for exploratory checks, especially when verifying hardware‑dependent behavior such as GPS drift or the effect of a case that blocks the antenna. Follow this step‑by‑step workflow on a physical device or an emulator.
1. Prepare the Device
- Enable Developer options → USB debugging.
- Under Developer options, turn on “Select mock location app” (we’ll use it later).
- Verify that Location is enabled in Settings → Location.
- Grant the test app location permission (if not already granted via adb:
adb shell pm grant).android.permission.ACCESS_FINE_LOCATION
2. Baseline Happy‑Path Test
- Open the app and navigate to the screen that triggers a location request (e.g., “Find my location”).
- Observe the UI: a spinner or progress indicator should appear, then a map pin or coordinates displayed.
- Use Google Maps or a GPS status app to confirm that the device actually has a fix (look for satellite count > 4).
- Walk a few meters; the displayed location should update smoothly.
3. Simulate GPS Loss
- Emulator: Open the Extended Controls → Location → set GPS to Off.
- Physical device: Run
adb shell cmd location setgpsdisabled 1. - Observe whether the app switches to the network provider (if available) or shows an appropriate message.
- Re‑enable GPS (
adb shell cmd location setgpsdisabled 0) and confirm recovery.
4. Test Mock Location
- Install a simple mock‑location app from Play Store (e.g., “Fake GPS Location”).
- In Developer options, select that app as the mock location source.
- Open the mock app, set a coordinate far from the real location (e.g., middle of the ocean).
- Return to the test app and verify:
- If the app is security‑sensitive (banking, payment), it should ignore the mock data and either show an error or fall back to the last known good fix.
- If the app is non‑critical, it may accept the mock location—ensure this is intentional and documented.
5. Background Location & Battery Optimization
- Start a location‑heavy feature (e.g., start a workout tracking session).
- Press Home to send the app to background.
- Go to Settings → Apps →
→ Battery → Battery optimization and select “Optimize” (or enable a battery‑saver mode). - Wait 10‑15 minutes, then restore the app.
- Check whether location updates continued (they should if a foreground service with
FOREGROUND_SERVICE_LOCATIONis used) or stopped (if relying only on a background service).
6. Doze Mode Validation
- Unplug the device, ensure battery level < 20 %.
- Leave the device idle (no touch, no charging) for at least 30 minutes.
- Wake the device and inspect logs (
adb logcat | grep Location) to see if location requests were deferred. - If your app uses
FusedLocationProviderClientwithsetPriority(PRIORITY_BALANCED_POWER_ACCURACY), you should see bursts of location updates during maintenance windows.
7. Accessibility Check
- Enable TalkBack (Settings → Accessibility → TalkBack).
- Trigger a location error (e.g., deny permission then try to start navigation).
- Verify that TalkBack reads the error message and that focus moves to a button that lets the user enable location or dismiss the dialog.
8. Battery Consumption Spot‑Check
- Reset battery stats:
adb shell dumpsys batterystats --reset. - Run a script that requests location every 5 seconds for 10 minutes (you can write a tiny Android app or use
adb shell cmd locationin a loop). - After the test, run
adb shell dumpsys batterystatsand look for the mA drain attributed to your package. - Compare against your budget; adjust request interval or priority if needed.
9. Clean‑up
- Disable mock location selection.
- Revoke any temporary permissions granted via adb.
- Return device to normal battery optimization settings.
Manual testing gives you confidence that the hardware and system interactions behave as expected. The next sections show how to automate the same checks.
---
Automated Testing with Espresso/UIAutomator
Automated tests can run on every commit, catching regressions in permission handling, provider switching, and background behavior. Below are patterns that work reliably on Android instrumentation tests.
1. Mocking the LocationManager
Android’s LocationManager is a system service; you can replace it with a fake implementation using Mockito or a custom Shadow class if you use Robolectric. For instrumentation tests, the easiest approach is to bind a fake service via the @Before method.
@RunWith(AndroidJUnit4::class)
class LocationFeatureTest {
private lateinit var locationManager: LocationManager
private lateinit var fakeProvider: FakeLocationProvider
@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext()
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
// Install a fake provider named "fake_gps"
fakeProvider = FakeLocationProvider("fake_gps")
locationManager.addTestProvider(
fakeProvider.name,
/*requiresNetwork=*/ false,
/*requiresSatellite=*/ false,
/*requiresCell=*/ false,
/*hasMonetaryCost=*/ false,
/*supportsAltitude=*/ true,
/*supportsSpeed=*/ true,
/*supportsBearing=*/ true,
/*power=*/ Criteria.POWER_LOW,
/*accuracy=*/ Criteria.ACCURACY_FINE
)
locationManager.setTestProviderEnabled(fakeProvider.name, true)
locationManager.setTestProviderStatus(
fakeProvider.name,
LocationProvider.AVAILABLE,
Bundle(),
System.currentTimeMillis()
)
}
@After
fun tearDown() {
locationManager.removeTestProvider(fakeProvider.name)
}
@Test
fun `location request returns fake coordinates`() {
val lifecycle = Scenario.launchActivity(MainActivity::class.java)
lifecycle.onActivity { activity ->
val fused = LocationServices.getFusedLocationProviderClient(activity)
val listener = mock<LocationCallback>()
fused.requestLocationUpdates(
LocationRequest.create().apply {
interval = 500
priority = Priority.PRIORITY_HIGH_ACCURACY
},
listener,
Looper.getMainLooper()
)
// Push a fake location
fakeProvider.pushLocation(
latitude = 37.422,
longitude = -122.084,
altitude = 0f,
accuracy = 5f
)
// Allow time for callback
Thread.sleep(2000)
verify(listener, atLeastOnce()).onLocationChanged(argThat { loc ->
loc.latitude == 37.422 && loc.longitude == -122.084
})
}
}
}
FakeLocationProvider (simplified):
class FakeLocationProvider(private val name: String) {
private var lastLocation: Location? = null
fun pushLocation(latitude: Double, longitude: Double, altitude: Float, accuracy: Float) {
val loc = Location(name).apply {
setLatitude(latitude)
setLongitude(longitude)
setAltitude(altitude)
setAccuracy(accuracy)
setTime(System.currentTimeMillis())
}
lastLocation = loc
}
fun getLastLocation(): Location? = lastLocation
}
This test validates that the app correctly consumes location updates from a provider you control, without needing a real GPS fix.
2. Testing Permission Flows
Use ActivityScenario to launch the app with a specific permission state.
@Test
fun `denying location permission shows rationale`() {
// Revoke permission before launch
val context = ApplicationProvider.getApplicationContext()
context.revokePermission(
context.packageName,
Manifest.permission.ACCESS_FINE_LOCATION
)
val scenario = Scenario.launchActivity(PermissionActivity::class.java)
scenario.onActivity { activity ->
// UI should show a rationale dialog
onView(withId(R.id.location_rationale_dialog))
.check(matches(isDisplayed()))
// Clicking “Grant” should request permission
onView(withText(R.string.grant)).perform(click())
// Verify that the system permission dialog appears
// (Espresso cannot interact with system dialogs; use UiDevice)
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
uiDevice.waitForExists(
UiObject2(By.text("Allow location access?")),
5000
)
}
}
3. Background Location & Doze Simulation
You can use adb shell cmd job to trigger maintenance windows or rely on the AlarmManager to schedule a job that checks for location updates. In an automated test, you can advance the device clock:
@Test
fun `location updates continue in Doze when using foreground service`() {
val scenario = Scenario.launchService(ForegroundLocationService::class.java)
// Simulate Doze by forcing idle maintenance window
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
uiDevice.executeShellCommand("cmd power set-idle true")
uiDevice.executeShellCommand("cmd power step-idle")
// Give the system a moment to process
Thread.sleep(5000)
// Verify that the service still received a location update
val received = scenario.serviceLocationUpdates.takeIf { it.size > 0 } ?: emptyList()
assertTrue(received.isNotEmpty())
}
4. Battery Impact Measurement in CI
While you cannot get precise mA values in a unit test, you can assert that the app does not exceed a reasonable number of location requests per minute:
@Test
fun `location request rate stays under threshold`() {
val requestCounter = AtomicInteger(0)
val locationCallback = object : LocationCallback() {
override fun onLocationChanged(location: Location?) {
requestCounter.incrementAndGet()
}
}
val scenario = Scenario.launchActivity(TrackingActivity::class.java)
scenario.onActivity { act ->
val fused = LocationServices.getFusedLocationProviderClient(act)
fused.requestLocationUpdates(
LocationRequest.create().apply {
interval = 2000 // 2 seconds
priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY
},
locationCallback,
Looper.getMainLooper()
)
}
// Let it run for 20 seconds → expect ≤ 10 requests
Thread.sleep(20_000)
fused.removeLocationUpdates(locationCallback)
assertTrue(requestCounter.get() <= 10, "Exceeded expected request rate")
}
These patterns give you a solid automated foundation. The next section shows how an autonomous, persona‑driven explorer can surface issues that scripted tests often miss.
---
Autonomous, Persona‑Driven Exploration with SUSA (SUSATest)
SUSA explores an app without predefined scripts, simulating real users with distinct behavior profiles. For location‑heavy features, this approach can reveal problems such as:
- A curious persona repeatedly taps the “Refresh location” button, exposing a race condition where rapid successive requests overload the fused provider.
- An impatient persona quickly switches apps while a location request is pending, highlighting a missing cleanup that leaves a pending callback and causes a memory leak.
- An elderly persona with enlarged font sizes triggers layout issues where the location‑permission rationale dialog gets clipped.
- An adversarial persona enables mock location via Developer options and attempts to submit a form with the falsified coordinates, testing whether the app validates the integrity of the location source.
How to Run SUSA for Location Testing
- Install the agent
pip install susatest-agent
- Point it at your APK or app URL
susatest run \
--apk path/to/app-release.apk \
--personas curious impatient elderly adversarial \
--location-mode gps \
--duration 15m \
--output ./susa-report
--location-mode gpstells SUSA to use the device’s GPS sensor (or the emulator’s GPS controls) and to also toggle mock‑location detection automatically.- The agent will install any necessary mock‑location apps, grant/revoke permissions, and vary network conditions while exercising the app.
- What the report contains
- A flow map showing each unique screen visited, annotated with location‑related events (e.g., “Location request sent”, “Provider switched to network”, “Mock location detected”).
- PASS/FAIL verdicts for predefined heuristics such as “No crash when location permission denied while a foreground service is active”.
- Metrics like average time to first fix, number of location updates per minute, and battery drain estimate derived from
dumpsys batterystats. - Security findings such as accidental logging of latitude/longitude in Logcat.
Example Finding
During a recent run on a ride‑hail app, SUSA’s adversarial persona discovered that when mock location was enabled, the app still sent the falsified coordinates to its backend for fare calculation, allowing a tester to spoof a trip and receive a discount. The report flagged this as a High severity security issue, prompting the developers to add a source‑authenticity check (using Location.isFromMockProvider()).
Because SUSA varies personas, network states, and system settings autonomously, it catches edge cases like the combination of Doze mode + background location + battery optimization that a static test matrix might overlook unless explicitly scripted.
---
Edge Cases That Only Appear in Production
Even with thorough manual and automated suites, certain bugs surface only after the app reaches real users. Below are the most common production‑only location pitfalls and how to mitigate them.
| Issue | Why It Happens in Production | Detection Strategy | ||
|---|---|---|---|---|
| GPS drift in urban canyons | Multipath signals cause the reported position to jump tens of meters, confusing geo‑fence logic. | Use a geo‑fence hysteresis (require the device to stay inside/outside for N seconds before triggering). Log the raw accuracy and speed to detect implausible jumps. | ||
| AGPS reliance on carrier data | Some networks suppress SUPL traffic, leading to longer TTFF (time to first fix) or reliance on cached coarse location. | In the app, measure time from onLocationChanged to first high‑accuracy fix; if > 30 s on Wi‑Fi only, warn the user to enable mobile data or restart location services. | ||
| Battery optimizer killing foreground services | Manufacturers (e.g., Xiaomi, OnePlus) aggressively stop services even when marked foreground, especially if the app is not whitelisted. | Provide a persistent notification with foreground service type FOREGROUND_SERVICE_TYPE_LOCATION and check ActivityManager.getRunningServices periodically; if the service disappears, prompt the user to add the app to the whitelist. | ||
| Doze + maintenance window misalignment | If your app requests location every 5 minutes, it may miss the maintenance window and receive no updates for hours. | Use FusedLocationProviderClient with setPriority(PRIORITY_BALANCED_POWER_ACCURACY) and setInterval(DEFAULT_INTERVAL_FASTEST to let the system batch requests; also schedule a WorkManager with setBackoffCriteria as a fallback. | ||
| Runtime permission revocation via system settings | Users can disable location for an app while it’s in the background; the app may not receive onProviderDisabled if it relies only on a manifest‑declared receiver. | Register a dynamic BroadcastReceiver for android.location.PROVIDERS_CHANGED in onStart/onStop and also listen to LocationManager.addGpsStatusListener. | ||
| External SD‑card mock GPX files | Power users place a GPX file on external storage and use a third‑party mock app that reads from the card; some mock apps fail to parse waypoint altitudes, causing the app to receive NaN values. | Validate every received Location: `if (!location.hasAccuracy() | location.getAccuracy() <= 0) { /* discard */ }`. | |
| Time‑zone changes while tracking | A user crosses a time‑zone border; the app uses System.currentTimeMillis() to timestamp logs, causing apparent backward jumps in location timelines. | Store timestamps as elapsedRealtimeNanos() for internal calculations, converting to wall‑clock only for display. | ||
| Concurrent location clients | Multiple libraries (ads, analytics, maps) each request location with different priorities, leading to conflicting updates and excessive battery use. | Centralize location requests through a singleton LocationRepository that merges requests and picks the highest priority; expose a removeAllListeners() method in onDestroy. | ||
Incorrect handling of location.hasBearing() | When the device is stationary, bearing may be unreliable; some apps rotate the map based on stale bearing, causing a jittery UI. | Only update map orientation if location.hasBearing() and location.getSpeed() > 0.5 m/s. | ||
| Security leakage via third‑party SDKs | An ad SDK logs Location to its own server without the app’s knowledge. | Run a network traffic inspection (e.g., using mitmproxy on a test device) and filter for any outbound requests containing lat/long parameters. |
Mitigating these issues often requires a combination of defensive coding (validating every location field), thoughtful use of the fused provider APIs, and clear user communication when the system limits location access.
---
Checklist for Location Services Testing
Copy this list into your test‑plan wiki or CI README. Tick each item before a release.
- [ ] Permission flow – grant, deny, revoke at runtime; app shows rationale and does not crash.
- [ ] Mock location detection – app either ignores mock data or warns the user; never uses mock for security‑critical flows.
- [ ] GPS loss & recovery – app switches to network provider or shows stale‑data warning; recovers when GPS returns.
- [ ] Network‑only provider – receives coarse fix within expected time; UI reflects reduced accuracy.
- [ ] Background location – updates continue when a foreground service with
FOREGROUND_SERVICE_TYPE_LOCATIONis used; stops correctly when only a background service runs. - [ ] Doze mode behavior – requests deferred to maintenance windows; no missed critical geo‑fence transitions.
- [ ] Battery impact – measured drain stays within budget for the chosen priority/request interval.
- [ ] Accessibility – TalkBack announces location errors and focuses an actionable control.
- [ ] Security/privacy – no raw latitude/longitude appears in Logcat, crash reports, or third‑party network traffic.
- [ ] Rapid provider switching – no crashes or request storms when GPS toggled rapidly.
- [ ] Altitude and bearing handling – app correctly processes optional fields and does not rely on them unless available.
- [ ] Time‑travel resilience – timestamps remain monotonic despite system clock changes.
- [ ] Multiple location consumers – app consolidates requests to avoid redundant updates.
- [ ] Manufacturer optimizations – app survives aggressive battery‑saver whitelisting checks on major OEMs.
- [ ] User‑facing guidance – when location is unavailable, the app offers a clear action (enable location, improve signal, try again later).
---
Closing Takeaways
Location services are a common source of both functional bugs and privacy concerns. A robust testing strategy blends three layers:
- Manual exploratory checks that validate real‑world hardware interactions (GPS drift, mock‑location attacks, battery‑optimizer quirks).
- Automated instrumentation tests that assert permission flows, provider switching, background behavior, and request rates using Espresso/UIAutomator and mocked location providers.
- Autonomous, persona‑driven exploration (exemplified by SUSA) that surfaces rare combinations—such as an impatient user toggling mock location while the device is entering Doze— that scripted tests rarely consider.
By following the test matrix, applying the manual step‑by‑step routine, encoding the checks in automated suites, and leveraging an autonomous explorer for production‑like variability, you can catch the majority of location‑related defects before they reach users. Keep the checklist handy, monitor battery and permission metrics in every release, and treat location as a first‑class concern in your architecture—your users (and regulators) will thank you.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free