Using mocks for the first time, not quite getting it

Discussion of the OCMock framework. If you have patches we would prefer you to send them to the mailing list, but attaching them to a topic is possible, too.

Using mocks for the first time, not quite getting it

Postby Mange » 12 Mar 2012, 10:37

Hello. I am trying to extend the coverage for a piece of code in my project, but its not going as well as i had hoped.

This is the piece of code i am testing. When running it (especially under automated circumstances using tools such as Jenkins) i will always get the WiFi response, which leaves the other part untested.
Code: Select all
- (NSString *)connectionType {
    Reachability *reach = [Reachability reachabilityForLocalWiFi];

    if (reach.currentReachabilityStatus == ReachableViaWiFi) {
   return @"WiFi";
    } else {
        reach = [Reachability reachabilityForInternetConnection];
        if (reach.currentReachabilityStatus == ReachableViaWWAN) {
            return @"MobileCarrier";
        }
    }
    return @"Undefined";
}


I then figured i should try mocking the Reachability class and return what ever value i wished for the method used, like this:
Code: Select all
id mock = [OCMockObject mockForClass:[Reachability class]];
[[[mock stub] andCall:@selector(ocMockReturn) onObject:self] currentReachabilityStatus];
...
- (NetworkStatus)ocMockReturn {
    return ReachableViaWWAN;
}


It seems however that this is never getting called. And from what i've read i might have to use method-swizzling instead, but then i would have to mock a certain object which in this case is not visible to me where i test the code. Is there anyway around all this so that i can also mock and test code such as this using this framework?
Mange
 
Posts: 1
Joined: 12 Mar 2012, 10:26

Re: Using mocks for the first time, not quite getting it

Postby erik » 13 Mar 2012, 00:00

The mock object is not called because the code in the connectionType method uses a real instance of the Reachability class, not the mock. The standard approach with mock objects on any platform is to allow substitution of collaborators. You need to figure out a way so that in your running app the connectionType method uses a real instance while in the tests it uses the mock.

One way to achieve this is to make "reach" an instance variable, that gets initialised with the real object but can be overriden from the test. For example:

Code: Select all
- (id)init
{
    // usual init stuff, then:
   reach = [Reachability reachabilityForLocalWiFi];
   return self;
}

- (void)setReach:(Reachability *)aReach
{
    reach = aReach;
}

- (NSString *)connectionType {

        if (reach.currentReachabilityStatus == ReachableViaWiFi) {
       return @"WiFi";
        } else {
            reach = [Reachability reachabilityForInternetConnection];
            if (reach.currentReachabilityStatus == ReachableViaWWAN) {
                return @"MobileCarrier";
            }
        }
        return @"Undefined";
    }



With this you can change your test as follows:

Code: Select all
    id mock = [OCMockObject mockForClass:[Reachability class]];
    [[[mock stub] andCall:@selector(ocMockReturn) onObject:self] currentReachabilityStatus];
    [whateverThatClassIs setReach:mock];


Depending on what you use for memory management (nothing, GC, ARC) you may have to throw in some retain/releases somewhere.
erik
 
Posts: 71
Joined: 10 Oct 2009, 15:22
Location: Hamburg, Germany


Return to OCMock



cron