How to write Espresso Tests which are mocking GPS locations and use them in Google Testlab?
Asked Answered
S

2

8

I recorded an espresso test with Espresso Recorder. I want to test some location changes in my app.

Currently I'm mocking the location with this code:

LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy( Criteria.ACCURACY_FINE );

String mocLocationProvider = LocationManager.GPS_PROVIDER;//lm.getBestProvider( criteria, true );

lm.addTestProvider(mocLocationProvider, false, false,
        false, false, true, true, true, 0, 5);
lm.setTestProviderEnabled(mocLocationProvider, true);

Location loc = new Location(mocLocationProvider);
Location mockLocation = new Location(mocLocationProvider); // a string
mockLocation.setLatitude(-26.902038);  // double
mockLocation.setLongitude(-48.671337);
mockLocation.setAltitude(loc.getAltitude());
mockLocation.setTime(System.currentTimeMillis());
mockLocation.setAccuracy(1);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
lm.setTestProviderLocation( mocLocationProvider, mockLocation);

I also added the permission to the debug manifest file:

<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

But unluckily I still get a Security Exception:

java.lang.SecurityException: mypackage.test from uid not allowed to perform MOCK_LOCATION

I want to run the recorded test case with the mocked location in Google Firebase Test Lab. How can I solve this problem?

Seleta answered 21/9, 2018 at 9:3 Comment(0)
S
16

Basically you have to enable the application in the devs options (select mock location app). Since you cannot control devices on google plateform, you have to use an ADB command to enable it.

appops set {yourPackageName} android:mock_location allow

As for example, this is what i am doing in a @before function for my mocking location tests :

@Before
fun grantPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        with(getInstrumentation().uiAutomation) {
            ...
            executeShellCommand("appops set " + InstrumentationRegistry.getTargetContext().packageName + " android:mock_location allow")
            Thread.sleep(1000)
            ...
        }
    }
}

Based on this approach, note that you can also create a snippet for your gradle task to do it automatically on your workstation if you plug any new device. See for example : How to set Allow Mock Location on Android Device before executing AndroidTest with uiautomator and espresso?

Hope this help !

Syllabub answered 8/10, 2018 at 8:59 Comment(1)
Updating this now that there's been a deprecated version of the Instrumentation Registry for quick reference for those not trying to dig around too hard for it: <br/> kotlin import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation executeShellCommand("appops set " + InstrumentationRegistry.getInstrumentation().context.packageName + " android:mock_location allow") Colchester
G
6

MrAurelien proposed a great solution. But it's better to implement it in a bit different way.

Thread.sleep(1000) makes your tests slower and flaky. Here's a different version which works stable.

@Before
fun grantPermission() {
    instrumentation.uiAutomation.executeShellCommandBlocking(
        "appops set ${appContext.packageName} android:mock_location allow"
    )
}

executeShellCommandBlocking is a custom utils function which waits for the appops command to complete.

fun UiAutomation.executeShellCommandBlocking(command: String) {
    val output = executeShellCommand(command)
    FileInputStream(output.fileDescriptor).use { it.readBytes() }
}

We use this solution in the Mapbox Android Navigation SDK.

Glutton answered 30/12, 2021 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.