3

in my fixture setUp i have the following

-(void)setUp{
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];
    //stub out the view stuff
    [[_vc stub] removeTask:OCMOCK_ANY];
    [[_vc stub] insertTask:OCMOCK_ANY];
}

There are 15 tests in the fixture, however, I need to actually test that those 2 methods are invoked, so I wrote 2 tests

-(void)someTest{
    [[_vc expect] removeTask:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [_vc verify];
}

but that test fails

i also tried

-(void)someTest{
    [[_vc stopMocking];
    [[_vc expect] removeTask:OCMOCK_ANY];
    [[_vc stub] removeTask:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [_vc verify];
} 

But the test still fails. Am I missing something, or is this just how OCMock works?

The only way I can make it work is like this

-(void)someTest{
    //re create and init the mock object 
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]]; 
    [[_vc expect] removeTask:OCMOCK_ANY]; 
    [[_vc stub] removeTask:OCMOCK_ANY]; 
    [_vc removeAllTasksFromList:taskList notInList:newTaskList]; 
    [_vc verify]; 
}
Zayin Krige
  • 3,229
  • 1
  • 35
  • 34

5 Answers5

8

Maybe the documentation should be clearer. What stopMocking does for a partial mock is to restore the real object, in your case the InboxViewController, to its original state. Calling stopMocking does not reset the mock object, which means it does not clear the stubs and expectations. You can always call stopMocking and then create a new mock for the same real object.

As was pointed out in another answer, stubbing and expecting the same method is generally better avoided, but if you have to do it, make sure to set up the expect before the stub; otherwise the stub will handle the invocations and the expect will never see them.

I know that traditionally many people recommend to use the setup method to set up the test subject. My personal experience, over the years, is that it's generally not worth it. Saving a couple of lines in each test might look attractive but in the end it does create coupling between the individual tests, making the suite more brittle.

Shaik Riyaz
  • 11,204
  • 7
  • 53
  • 70
Erik Doernenburg
  • 2,933
  • 18
  • 21
2

Seems that you need to create a new mock to expect a method that you already stubbed. I would recommend you to reconsider to use a partial mock in all your test cases, and if you want so, extract this:

[[_vc stub] removeTask:OCMOCK_ANY];
[[_vc stub] insertTask:OCMOCK_ANY];

into a helper method and call that method from the tests you really need it, removing it from your setUp method.

And, small tip :), you should call [super setUp] at the beginning of your setUp implementation.

e1985
  • 6,239
  • 1
  • 24
  • 39
  • This is just a code sample. I am actually calling super setup. It seems that stubbing before expecting doesn't work. But what I don't understand is why doesn't stop mocking and then expect then stub work – Zayin Krige May 24 '13 at 17:01
2

You can do the following to remove stubs from an OCMockObject, which will enable you to keep the stub code in your -(void)setUp, but still allow you to add an expect in a later test.

Add the following category to your test to return the OCMockObject ivar.

@interface OCMockObject (Custom)
- (void)removeStubWithName:(NSString*)stubName;
@end

@implementation OCMockObject (Custom)
- (void)removeStubWithName:(NSString*)stubName {
    NSMutableArray* recordersToRemove = [NSMutableArray array];
    for (id rec in recorders) {
        NSRange range = [[rec description] rangeOfString:stubName];
        if (NSNotFound == range.location) continue;
        [recordersToRemove addObject:rec];
    }
    [recorders removeObjectsInArray:recordersToRemove];
}
@end

Example usage: Make sure you remove the stub for a method BEFORE you add the expect.

-(void)someTest{
    [_vc removeStubWithName:@"removeTask"];
    [[_vc expect] removeTask:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [_vc verify];
}

This will allow your test to run as you expect.

It's a useful hack, at least until the OCMock developers allow this functionality.

Drew H
  • 1,226
  • 1
  • 12
  • 13
1

A couple of things to note:

  1. You should keep a reference to the actual object you're mocking. Invoke mock setup/verification on the mock reference, and methods you're testing on the actual object.
  2. You shouldn't stub and expect the same method call. The stub will match, and the expectation won't pass.

I'm assuming you're stubbing in setUp because you have some methods that may or may not be called in the tests. If so, you can structure your tests like this:

static InboxViewController *_vc;
static id mockInbox;

-(void)setUp{
    _vc = [[InboxViewController alloc] init];
    mockInbox = [OCMockObject partialMockForObject:_vc];
    //stub out the view stuff
    [[mockInbox stub] removeTask:OCMOCK_ANY];
    [[mockInbox stub] insertTask:OCMOCK_ANY];
}

-(void)someTest{
    [[mockInbox expect] somethingIExpectForThisTest:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [mockInbox verify];
}

-(void)someOtherTest{
    [[mockInbox expect] someOtherThingIExpectForThisTest:OCMOCK_ANY];
    [_vc doSomethingElse];
    [mockInbox verify];
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • Interestingly enough, this raises another issue with ocmock. You can't stub before expect. It fails the test. I've actually asked the ocmock devs about this but haven't received a response yet – Zayin Krige May 25 '13 at 17:03
  • 1
    One of the answers has more details but, basically, if you create the stub first it'll handle all the invocations, the expectation never sees it, and then the mock will complain in verify. Normally, you either want to expect a method or stub it, but not both. I guess what's still missing in OCMock is an "atLeastOnce" modifier so that expecting methods that can be called multiple times gets easier. – Erik Doernenburg May 25 '13 at 17:34
0

The order in which the stub and expect calls are given is important to the partial mock. Stubbing the method before expecting it will mean that any messages sent to the stubbed method will never meet the expectation.

On the other hand, stopMocking just means the partial mock will stop being associated to the real object. The mock still keeps its recorders (stubs and expectations) and works as usual. You can verify this by sending removeAllTasksFromList:notInList to your real object, which in this case is not assigned to any variable. You'll see in this case that the message does reach your object's implementation. The mock will still fail the verify call, though. In general, you should be calling methods on your real object. The partial mock will still intercept the messages.

As mentioned in the other answer, the best way around this is to create a new partial mock and call expect before stubbing the method. You could even implement a helper:

- (void) setUpPartialMockWithExpect:(BOOL)needExpect {
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];

    if( needExpect )
        [[_vc expect] removeTask:OCMOCK_ANY];

    //stub out the view stuff
    [[_vc stub] removeTask:OCMOCK_ANY];
    [[_vc stub] insertTask:OCMOCK_ANY];
}

And call that in each of your -(void)test...

Dave FN
  • 652
  • 1
  • 14
  • 29