CLLocationManagerDelegate mocking on users denial

1k Views Asked by At

-- Final Edit: More accurate question Unit Test CLLocationManager implementation

I'm trying to mock CLLocationManagerDelegate to handle users denial about being geolocated

id locationManager = [OCMockObject niceMockForClass:[CLLocationManager class]];
[[[locationManager stub] andReturnValue:@(kCLAuthorizationStatusDenied)] authorizationStatus];

id delegateTarget = [OCMockObject niceMockForProtocol:@protocol(CLLocationManagerDelegate)];
[[delegateTarget expect] locationManager:locationManager didFailWithError:[OCMArg any]];

[locationManager setDelegate:delegateTarget];
[locationManager startUpdatingLocation];

[delegateTarget verify];

However this code doesn't work, I keep getting this error:

Name: NSInternalInconsistencyException File: Unknown Line: Unknown Reason: OCMockObject[CLLocationManagerDelegate]: expected method was not invoked: locationManager:OCMockObject[CLLocationManager] didFailWithError:

How can I be sure that the CLLocationManagerDelegate method locationManager:didFailWithError: is getting called?

Edit: If I use partialMock instead of niceMock for my CLLocationManager instance:

CLLocationManager *trueLocationManager = [[CLLocationManager alloc] init];
id locationManager = [OCMockObject partialMockForObject:trueLocationManager];
[[[locationManager stub] andReturnValue:@(kCLAuthorizationStatusDenied)] authorizationStatus];

id delegateTarget = [OCMockObject mockForProtocol:@protocol(CLLocationManagerDelegate)];
[[delegateTarget expect] locationManager:trueLocationManager didFailWithError:[OCMArg any]];

[locationManager setDelegate:delegateTarget];
[locationManager startUpdatingLocation];

[delegateTarget verify];

I get this:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'OCMockObject[CLLocationManagerDelegate]: unexpected method invoked: locationManager: didChangeAuthorizationStatus:2 expected: locationManager: didFailWithError:'

So I thought I got it and added the code below before my delegateTarget expectation of didFailWithError

[[delegateTarget expect] locationManager:trueLocationManager didChangeAuthorizationStatus:2]; // kCLAuthorizationStatusDenied = 2

And the result is:

Name: NSInternalInconsistencyException File: Unknown Line: Unknown Reason: OCMockObject[CLLocationManagerDelegate] : 2 expected methods were not invoked: locationManager: didChangeAuthorizationStatus:2 locationManager: didFailWithError:

Really don't know what I'm doing wrong...

Edit2: for Ilea Cristian

CLLocationManager *trueLocationManager = [[CLLocationManager alloc] init];
id locationManager = [OCMockObject partialMockForObject:trueLocationManager];
[[[locationManager stub] andReturnValue:@(kCLAuthorizationStatusDenied)] authorizationStatus];

id delegateTarget = [OCMockObject mockForProtocol:@protocol(CLLocationManagerDelegate)];
[[delegateTarget expect] locationManager:locationManager didChangeAuthorizationStatus:2]; // kCLAuthorizationStatusDenied = 2
[[delegateTarget expect] locationManager:locationManager didFailWithError:[OCMArg any]];

[locationManager setDelegate:delegateTarget];
[locationManager startUpdatingLocation];

[delegateTarget verify];

I got this:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'OCMockObject[CLLocationManagerDelegate]: unexpected method invoked: locationManager: didChangeAuthorizationStatus:2 expected: locationManager:OCPartialMockObject[CLLocationManager] didChangeAuthorizationStatus:2 expected: locationManager:OCPartialMockObject[CLLocationManager] didFailWithError:'

Looks like locationManager used in the delegate callback is the trueLocationManager not the locationManager (partialMock)

-- Final Edit: More accurate question Unit Test CLLocationManager implementation

2

There are 2 best solutions below

1
On

You have created a "nice mock" for your LocationManager. This means that your calls to setDelegate: and startUpdatingLocation will be silently ignored. You could switch this to a partial mock, but are you just testing the LocationManager? You should only need to test your implementation of the location manager delegate -- you can just call those delegate methods directly.

4
On

You have this line:

[locationManager setDelegate:delegateTarget];

This means that when locationManager:didFailWithError: is called, the object that is passed for the CLLocationManager parameter will be your OCMock object named locationManager - AND NOT trueLocationManager.

Your expectation:

[[delegateTarget expect] locationManager:trueLocationManager didFailWithError:[OCMArg any]];

So, you are expecting a call that passes the trueLocationManager, but instead the partial mock is passed. Try expecting the partial mock to be passed.

OCMock partial mocks, like locationManager, are just proxy wrappers over the real object, but their type is different (that's why you used id instead of real types).

This magic works because of the way Objective-C handles calls through message passing. If you read a bit about the NSProxy and then you look at the OCMock implementation you will truly understand how some backstage stuff works.

EDIT:

Personally I don't know what do you really want to test. The Apple framework? None of the entities there are your classes/protocols. What are you testing?

I guess you have a ViewController that implements the CLLocationManagerDelegate protocol. Mock that class. If your location manager is a property, stub it to return your fake location manager (so that internally the vc uses the fake). Or set the fake locationManager.delegate to be your ViewController. Start the fake location manager.

Expect the ViewControllers delegate methods to be called. If your location manager is a singleton, then it's a bit tricky to replace it with a fake/stub.