I would like to write UI tests in Swift that make screenshots of various places of a map in our app. In order to do this, I need to simulate fake GPS data during the test.
There are some solutions like this one (https://blackpixel.com/writing/2016/05/simulating-locations-with-xcode-revisited.html) that use GPX files and simulate the location with Debug > Simulate Location
in Xcode, but I need this to be completely automated. Ideal would be something similar to the LocationManager
in Android.
I have had similar problems when writing UI tests, because the simulator / tethered device can't do everything you might want. What I do is write mocks that mimic the desired behavior (of something I would normally have no control over).
Substituting a custom location manager for the CLLocationManager will allow you to take full control of location updates, as you can programmatically send the location updates through the CLLocationManagerDelegate method: locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
.
Create a class MyLocationManager
, make it a subclass of CLLocationManager
, and have it override all the methods you call. Don't call super in the overridden methods, as CLLocationManager should never actually receive a method call.
class MyLocationManager: CLLocationManager {
override func requestWhenInUseAuthorization() {
// Do nothing.
}
override func startUpdatingLocation() {
// Begin location updates. You can use a timer to regularly send the didUpdateLocations method to the delegate, cycling through an array of type CLLocation.
}
// Override all other methods used.
}
The delegate
property doesn't need to be overridden (and can't be), but you have access to it as a subclass of CLLocationManager.
To use MyLocationManager
you should pass in launch arguments that tell your app if it is a UITest or not. In your test case's setUp
method insert this line of code:
app.launchArguments.append("is_ui_testing")
Store CLLocationManager as a property that is a MyLocationManager when testing. When not testing CLLocationManager will be used as normal.
static var locationManger: CLLocationManager = ProcessInfo.processInfo.arguments.contains("is_ui_testing") ? MyLocationManager() : CLLocationManager()