Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 326
Marcel Gangwisch Avatar asked Sep 21 '18 09:09

Marcel Gangwisch


Video Answer


2 Answers

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 !

like image 81
MrAurelien Avatar answered Nov 15 '22 21:11

MrAurelien


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.

like image 44
VadzimV Avatar answered Nov 15 '22 21:11

VadzimV