3

So I have a class I wrote some test cases for. This class has these two methods:

- (void)showNextNewsItem {
    self.xmlUrl = self.nextNewsUrl;
    [self loadWebViewContent];
}

- (void)showPreviousNewsItem {
    self.xmlUrl = self.previousNewsUrl;
    [self loadWebViewContent];
}

Could be refactored and this is quite primitive, but nevertheless I just want to make sure next loads next and previous loads previous. So I use OCMock to instantiate a OCMockObject for my SUT class like this:

- (void)testShowNextOrPreviousItemShouldReloadWebView {

    id mockSut = [OCMockObject mockForClass:[NewsItemDetailsViewController class]];

    [[[mockSut expect] andReturn:@"http://www.someurl.com"] nextNewsUrl];
    [[mockSut expect] loadWebViewContent];

    [[[mockSut expect] andReturn:@"http://www.someurl.com"] previousNewsUrl];
    [[mockSut expect] loadWebViewContent];

    [mockSut showNextNewsItem];
    [mockSut showPreviousNewsItem];

    [mockSut verify];
}

The problem lies in the two lines actually calling the methods that do something to be verified. OCMock now tells me, that invoking showNextNewsItem and showPreviousNewsItem are not expected. Of course, it's not expected because I am in the test and I only expect certain things to happen in the production code itself.

Which part of the mocking concept didn't I understand properly?

Sebastian Wramba
  • 10,087
  • 8
  • 41
  • 58

2 Answers2

2

It's generally confusing to mock the class under test, but if you want to do that, you need a "partial mock", so that you can call methods without stubbing them and have them execute the normal methods.

This appears to be supported in OCMock according to the docs.

Don Roby
  • 40,677
  • 6
  • 91
  • 113
  • Thank you - that is correct. But unfortunately, iOS projects tend to accumulate "behavior-driven" methods instead of functions with a clear return value. This way I can only test the correct invocation of method calls rather than a result I expect. Maybe I should think about refactoring this. ;-) – Sebastian Wramba Jan 19 '13 at 18:14
  • Your own answer seems to suggest that you figured out how to do this with a partial mock. Sorry if it wasn't clear, but I was trying to point you to exactly that, though I was also saying that it would be better to not mock the class you're testing. Refactoring when you find the need for this is almost always a good idea. – Don Roby Jan 19 '13 at 19:35
  • That's right. The idea came to my mind probably at the same time you posted this. I'm gonna mark your answer as correct though - I just didn't want to post the whole code in a comment (which isn't even possible). Thank you very much! – Sebastian Wramba Jan 19 '13 at 23:04
  • Thanks. It all makes sense now. I would have included code here except I'm really not an Objective-C programmer. Good that you figured it out and put your code in an answer. Have an upvote for that... – Don Roby Jan 19 '13 at 23:09
2

I found the solution. Using a partialMock on the object does exactly what I want. This way, calls I explicitly define are mocked and I call the methods that are under test on the "not-mocked" object.

- (void)testShowNextOrPreviousItemShouldReloadWebView {

    NewsItemDetailsViewController *sut = [[NewsItemDetailsViewController alloc] init];

    id mockSut = [OCMockObject partialMockForObject:sut];

    [[[mockSut expect] andReturn:@"http://www.someurl.com"] nextNewsUrl];
    [[mockSut expect] loadWebViewContent];

    [[[mockSut expect] andReturn:@"http://www.someurl.com"] previousNewsUrl];
    [[mockSut expect] loadWebViewContent];

    [sut showNextNewsItem];
    [sut showPreviousNewsItem];

    [mockSut verify];
}
Sebastian Wramba
  • 10,087
  • 8
  • 41
  • 58