Faking CoreLocation... Inconceivable!

Matt Mongeau

I decided to get more familiar with integration testing iOS applications. In the past I’ve found trying to do integration testing analogous with climbing the side of a cliff.

''

Given that I’m no Dread Pirate Roberts this has proven very challenging in the past. To challenge myself even further I decided to write an integration test around CoreLocation. In the simulator I can set a location for myself and to some degree I could test with this, but I wanted to be able to have different test scenarios given different locations.

''

One solution was to use something like FTLocationSimulator and do the following:

#ifdef FAKE_CORE_LOCATION
    self.locationManager = [[FTLocationSimulator alloc] init];
#else
    self.locationManager = [[CLLocationManager alloc] init];
#endif

But I’m not a big fan of adding conditional flags in my code. Also I don’t feel like I have a lot of control over what happens in my code. Instead I prefer doing something closer to dependency injection. Wherever I would normally do [[CLLocationManager alloc] init] I instead do [MyLocationManager sharedInstance]. MyLocationManager is a singleton which will delegate methods to it’s own locationManager. The basic setup looks something like:

#import "MyLocationManager.h"
#import <CoreLocation/CoreLocation.h>

@implementation MyLocationManager
@synthesize locationManager;

static MyLocationManager *sharedSingleton;
+ (id)sharedInstance {
    static BOOL initialized = NO;
    if(!initialized) {
        initialized = YES;
        sharedSingleton = [[MyLocationManager alloc] init];
    }
    return sharedSingleton;
}
- (id) init {
    if (self = [super init]) {
        locationManager = [[CLLocationManager alloc] init];
    }
    return self;
}
@end

And my fake location manager:

#import "CLLocationManagerFake.h"
#import <CoreLocation/CoreLocation.h>

@implementation CLLocationManagerFake
@synthesize desiredAccuracy;
@synthesize location;

-(id) init {
    if (self = [super init]) {
        self->location = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0];
    }
    return self;
}

- (void)startUpdatingLocation {
    [self.delegate locationManager:self didUpdateToLocation:self->location fromLocation:self->location];
}

@end

And in my tests I can do the following:

CLLocationManagerFake *fake = [[CLLocationManagerFake alloc] init];
CLLocation *fakeLocation = [[CLLocation alloc] initWithLatitude:42.356426 longitude:-71.061993];
[fake setLocation:fakeLocation];
[[TTimeLocationManager sharedInstance] setLocationManager:fake];

Now I have complete control in my tests without polluting my actual code. If I want to use a different fake or have more control over specific scenarios I am able to do so with ease. I can also keep my test dependent code out of my final target.

''

“HE DIDN’T FALL? INCONCEIVABLE. ”