OCMock "expected method was not invoked" even though I'm calling it myself
Asked Answered
W

2

6

I'm trying to set up a simple OCMock unit test in an iOS project, just to familiarize myself with the framework.

I have a mocked DataLoader class, and even though I'm calling the method myself, my expectation fails:

- (void)testSimpleMocking {
    // Mock the class
    id mock = [OCMockObject niceMockForClass:[DataLoader class]];

    // Override the 'dispatchLoadToAppDelegate:' to be a no-op
    [[[mock stub] andReturn:nil] dispatchLoadToAppDelegate:[OCMArg any]];

    // Expect the method to be called
    [[mock expect] dispatchLoadToAppDelegate:[OCMArg any]];

    // Call the method
    [mock dispatchLoadToAppDelegate:nil];

    // Verify
    [mock verify];
}

However, when I run this test, I receive the error:

/Users/Craig/projects/MyApp/Unknown.m: -[MockingDataLoaderTest testSimpleMocking] : OCMockObject[DataLoader]:
expected method was not invoked: dispatchLoadToAppDelegate:<OCMAnyConstraint: 0x1a3d890>

How is this possible, when I am calling the method myself?

Edit: A more complex case:

- (void)testDataLoaderWaitsForDownload {
    id mock = [OCMockObject niceMockForClass:[DataLoader class]];
    id metadataItem = [OCMockObject niceMockForClass:[NSMetadataItem class]];

    // Prepare NSMetadataItem
    [[[metadataItem expect] andReturn:nil] valueForAttribute:NSMetadataItemURLKey];

    // CODERUN
    [mock waitForDownload:metadataItem thenLoad:YES];

    //VERIFY
    [metadataItem verify];
}

And the implementation of the waitForDownload:thenLoad: method:

- (void)waitForDownload:(NSMetadataItem *)file thenLoad:(BOOL)load {
    NSURL *metadataItemURL = [file valueForAttribute:NSMetadataItemURLKey];
    ...

Fails with the error:

Unknown.m:0: error: -[MockingDataLoaderTest testDataLoaderWaitsForDownload] : OCMockObject[NSMetadataItem]: expected method was not invoked: valueForAttribute:@"kMDItemURL"
Wafd answered 25/6, 2013 at 12:40 Comment(1)
In your edit you are calling waitForDownload on your mock object, so the real method is not being invoked. Depending where you're going with this you want DataLoader to be a real object or a partial mock.Polluted
P
6

In your test, stub is taking priority because it was called first. If you switch the order of your expect and stub your test should pass.

The reason you would use both expect and stub together (with the same argument expectations) is to ensure that at least one call occurs, but then to respond to subsequent calls without failure.

If you truly looking for just one call to a method, just add the andReturn: to the expect clause...

- (void)test_dispatchLoadToAppDelegate_isCalledExactlyOnce {
    // Mock the class
    id mock = [OCMockObject niceMockForClass:[DataLoader class]];

    // Expect the method to be called
    [[[mock expect] andReturn:nil] dispatchLoadToAppDelegate:[OCMArg any]];

    // Call the method
    [mock dispatchLoadToAppDelegate:nil];

    // Verify
    [mock verify];
}

An alternate scenario:

- (void)test_dispatchLoadToAppDelegate_isCalledAtLeastOnce {
    // Mock the class
    id mock = [OCMockObject niceMockForClass:[DataLoader class]];

    // Expect the method to be called
    [[[mock expect] andReturn:nil] dispatchLoadToAppDelegate:[OCMArg any]];

    // Handle subsequent calls
    [[[mock stub] andReturn:nil] dispatchLoadToAppDelegate:[OCMArg any]];

    // Call the method
    [mock dispatchLoadToAppDelegate:nil];

    // Call the method (again for fun!)
    [mock dispatchLoadToAppDelegate:nil];

    // Verify
    [mock verify];
}

For this particular case, it looks like you could use niceMockForClass but if you wanted the stub to return a non-nil, then you'd have to call stub either way.

Polluted answered 25/6, 2013 at 23:23 Comment(2)
I would add the andReturn: call to the expectation... I know expecting and stubbing are different things but to me it looks less redundant.Foxing
Thanks Ben for your reply - that makes more sense, but I'm encountering an issue now with an expectation (not stubbed) that is again called manually, but is failing the expectation. (I added the new situation to my question.)Wafd
A
2

Ben Flynn is correct that reversing the order of stub and expect should make your test pass, but I'd go a step further and suggest you should remove the stub call. The way you've written this test suggests that stub is a prerequisite to expect, which it's not.

expect means the method must be invoked once and only once (for each expectation). stub means the method may be called zero or more times. Generally you expect calls that are important to the test, and stub things that are side effects. Or use a nice mock and only set expectations for the important calls.

Agaric answered 26/6, 2013 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.