Nbfour Nbfour - 2 months ago 21
Swift Question

Programmatically simulate GPS location in iOS tests

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.

Answer Source

I have had similar problems when writing UI tests, because the simulator / tethered device can't do everything you might want. What 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()