1

I'm working with a very simple web service that uses a base class to reuse some commonly used functionality. The main method under test simply builds a url and then it uses a super / base method with this argument.

- (void)getPlacesForLocation:(Location *)location WithKeyword:(NSString *)keyword
{
    NSString *gps = [NSString stringWithFormat:@"?location=%@,%@", location.lat, location.lng];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.baseurl, gps]];
    [super makeGetRequestWithURL:url];
}

Here is the base method definition

@implementation WebService
@synthesize responseData = _responseData;

- (id)init
{
    if (self == [super init])
    {
        self.responseData = [NSMutableData new];        
    }

    return self;
}

- (void)makeGetRequestWithURL:(NSURL *)url
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    request.HTTPMethod = @"GET";

    [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

In my test I created a partial mock because I still want to call into my object under test, but I need the ability to verify the super method is invoked in a specific way.

- (void)testGetRequestMadeWithUrl
{
    self.sut = [[SomeWebService alloc] init];
    Location *location = [[Location alloc] initWithLatitude:@"-33.8670522" AndLongitude:@"151.1957362"];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.sut.baseurl, @"?location=-33.8670522,151.1957362"]];
    id mockWebService = [OCMockObject partialMockForObject: self.sut];
    [[mockWebService expect] makeGetRequestWithURL:url];
    [self.sut getPlacesForLocation:location WithKeyword:@"foo"];
    [mockWebService verify];
}

Yet when I run this test I fail with the following error:

expected method was not invoked: makeGetRequestWithURL:https://...

I can tell this method isn't being mock because if I put an NSLog into the base method it shows up when I run the ocunit test (clearly it's running, just not mocking it as I would like).

How can I modify my test / refactor my implementation code to get the assertion I'm looking for?

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

1 Answers1

3

This is an interesting case. My assumption is that if you replace "super" with "self" then everything will work as expected, ie.

- (void)getPlacesForLocation:(Location *)location WithKeyword:(NSString *)keyword
{
    NSString *gps = [NSString stringWithFormat:@"?location=%@,%@", location.lat, location.lng];
    NSURL *url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", self.baseurl, gps]];
    [self makeGetRequestWithURL:url];
}

The issue is that the partial mocks are implemented by creating subclasses on the fly. When using "super" the lookup for the method starts at the parent class, the base class in your case, which means the runtime never sees the method implemented in the subclass created by the partial mock.

A different answer to your question is to change the design. Rather than using a class hierarchy use two classes. One class would be responsible for creating the URL, the other for making the requests. Then you wouldn't need partial mocks because you could simply substitute the request maker. See Single Responsibility Principle [1] and Composition over Inheritance [2].

[1] http://en.wikipedia.org/wiki/Single_responsibility_principle

[2] http://en.wikipedia.org/wiki/Composition_over_inheritance

Erik Doernenburg
  • 2,933
  • 18
  • 21
  • Awesome! thanks for the blazing fast reply! Any issue with me using self instead of SUPER here? – Toran Billups Mar 20 '12 at 12:11
  • Can't think of any. In fact, it's likely what you wanted anyway. What would be your expectation if you provided an override for makeGetRequestWithURL: in the class that contains the getPlacesForLocation:WithKeyword: method? You would probably expect that override to be called, right? By using super you're saying you don't want that to happen and you definitely want the impl from the parent class. – Erik Doernenburg Mar 20 '12 at 16:11