1

I have been trying to add some (logic) unit tests to my code recently. I've set up the tests with Kiwi, I like the BDD style and the syntax.

My problem now is that I'm trying to test some code that relies on CLLocationManager sending a correct locationManager:didUpdateToLocation:fromLocation:. However, this never happens when I run the test, presumably because CLLocationManager thinks it's not authorised. For the record, I have added a .gpx file to the test target and edited the scheme to use that file as the location (under Edit Scheme... -> Test -> Info). The same code works fine when I run the full app in the simulator. Any idea how I can get (simulated) location updates to be sent in a test case?

DaGaMs
  • 1,521
  • 17
  • 26

2 Answers2

4

Use dependency injection to specify the location manager you'd like to use. You can either:

  • Specify it as an argument to the initializer (constructor injection)
  • Set it as a property (setter injection)

Try to use constructor injection if you can.

Then for real use, pass in a real CLLocationManager. But for test use, provide a fake that you can trigger to send the desired method, with preset test arguments. This also makes your test deterministic by removing any reference to your actual location.

Jon Reid
  • 20,545
  • 2
  • 64
  • 95
  • 1
    I was worried that was the only way, but that means I need to refactor the existing code quite a bit. The class that holds the CLLocationManager is a singleton, so if I want to use `initWithManager:` instead of just `init`, I can't use the `sharedController` class method to get an instance. Then, you're suggesting to create a mock `CLLocationManager`, intercept `startUpdatingLocation` and `stopUpdatingLocation` and somehow manually feed and array of `CLLocation` instances to `locationManager:didUpdateLocations:`? What a pain... – DaGaMs Feb 06 '13 at 10:48
  • This answer is correct, but as I point out below, I still decided to take a different approach for simplicity's sake. – DaGaMs Feb 06 '13 at 12:36
  • 2
    Singletons are the bane of unit testing. There's another way. Expose it as a property for setter injection, but have your initializer set up a reasonable default — in this case, the desired singleton. Your tests can then replace it. – Jon Reid Feb 07 '13 at 15:01
1

I ended up going down a different way: I converted my logic test to an application test, so that the test actually runs alongside the app in the simulator. This has the definitive advantage that I don't have to jump through hoops to get [NSBundle mainBundle] and CLLocationManager to work exactly like in the app. I'd have preferred the conceptual cleanliness of a separate logic test, but I don't think it makes sense to rewrite code just for that.

DaGaMs
  • 1,521
  • 17
  • 26