0

I'm writing a test to verify location services are started when a button click occurs. This requires a very simple if statement to make sure the phone has location services available.

A working test right now looks like this

- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled
{
    [[[self.locationManager stub] andReturnValue:[NSNumber numberWithBool:true]] locationServicesEnabled];
    [[self.locationManager expect] startUpdatingLocation];
    [self.sut buttonClickToFindLocation:nil];
    [self.locationManager verify];
}

The now tested implementation looks like this

- (IBAction)buttonClickToFindLocation:(id)sender
{
    if ([self.locationManager locationServicesEnabled])
    {
        [self.locationManager startUpdatingLocation];
    }
}

All good except the method was deprecated in iOS 4.0. So now I need to use the Class Method [CLLocationManager locationServicesEnabled] instead.

The problem is I can't seem to find if ocmock supports this functionality and if it doesn't how should I get around this issue for now.

Toran Billups
  • 27,111
  • 40
  • 155
  • 268

4 Answers4

3

hmmm, you could use methodExchange. Just make sure you exchange the method back to original after your done with it. It seems hacky, but I haven't found a better solution. I have done something similar for stubbing [NSDate date]

@implementation

static BOOL locationManagerExpectedResult;

- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled
{
    locationManagerExpectedResult = YES;

    method_exchangeImplementations(
       class_getClassMethod([CLLocationManager class], @selector(locationServicesEnabled)) , 
       class_getClassMethod([self class], @selector(locationServicesEnabledMock))
    );

    [self.sut buttonClickToFindLocation:nil];
}

+ (BOOL)locationServicesEnabledMock
{
    return locationManagerExpectedResult;
}

@end

EDIT: I thought you were verifying, but it seems like you are stubbing. Updated code

aryaxt
  • 76,198
  • 92
  • 293
  • 442
2

The simplest approach is to override locationServicesEnabled in a category in your unit test class:

static BOOL locationServicesEnabled = NO;

@implementation CLLocationManager (UnitTests)

+(BOOL)locationServicesEnabled {
    return locationServicesEnabled;
}

@end

...

-(void)tearDown {
    // reset to default after each test
    locationServicesEnabled = NO;
    [super tearDown];
}

It will override the superclass method only at test time, and you can set the static global to an appropriate value in each test.

Alternatively, you could wrap the check in your own instance method, and use a partial mock.

In the class under test:

-(BOOL)locationServicesEnabled {
    return [CLLocationManager locationServicesEnabled];
}

In your test:

-(void)testSomeLocationThing {
    MyController *controller = [[MyController alloc] init];
    id mockController = [OCMockObject partialMockForObject:controller];
    BOOL trackingLocation = YES;
    [[[mockController stub] andReturnValue:OCMOCK_VALUE(trackingLocation)] locationServicesEnabled];

    // test your controller ...
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
1

I don't think it does. The only approach I can think of would be to use a partial mock and then use runtime calls to swizzle in the implementation you need.

Doable, but complex.

A more pattern orientated solution might be to extract the checking for location services out to sit behind a protocol. Then you can simply use a mock for the protocol's implementation during testing to return YES or NO as your require. As the actual implementation would do nothing but return [CLLocationManager locationServicesEnabled] you could get away with not testing it.

drekka
  • 20,957
  • 14
  • 79
  • 135
0

This is supported by OCMock:

[[[[mockLocationManagerClass stub] classMethod] andReturnValue:OCMOCK_VALUE(YES)] locationServicesEnabled];
psobko
  • 1,548
  • 1
  • 14
  • 24