0

I'm testing a method using OCMock. The method is as follows:

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(NSDictionary *)parameters
                        block:(void (^)(id responseObject, NSError *error))block
{
    return [self GET:URLString parameters:parameters success:^(NSURLSessionDataTask * __unused task, id responseObject) {
        block(responseObject, nil);
    } failure:^(NSURLSessionDataTask * __unused task, NSError *error) {
        block(nil, error);
    }];
}

This test fails with "expected method was not invoked":

id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];

NSString *URLstring = @"test";
NSDictionary *parameters = nil;

void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};

void (^successBlock)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask * __unused task, id responseObject) {
    block(responseObject, nil);
};

void (^failureBlock)(NSURLSessionDataTask *, NSError *) = ^(NSURLSessionDataTask * __unused task, NSError *error) {
    block(nil, error);
};

[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:successBlock failure:failureBlock];

[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];

[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];

But this passes:

id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];

NSString *URLstring = @"test";
NSDictionary *parameters = nil;

void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};

[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];

[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];

[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];

Why does the first test fail and how can I make it pass?

I've put an example project on GitHub demonstrating the issue: https://github.com/paulyoung/OCMockExample

Paul Young
  • 1,489
  • 1
  • 15
  • 34
  • Could it be possible the block comparison on the matcher is failing because the block is copied at some point by OCMock? You may have to settle for `[OCMArg isNotNil]` or go more heavy-handed and use `__block` local variables to ensure the correct blocks are invoked. – Ash Furrow Jan 19 '14 at 02:31
  • I guess that OCMocks copes blocks therefore there are moved from stack to heap and then have different addresses. Please check if test fails if you pass in all places one copy of block instead of local variable. – KamilPyc Jan 19 '14 at 09:01
  • @AshFurrow - I've added a link to a project on GitHub which demonstrates the issue. I believe I've tried your suggestion of using `__block` but in case I misunderstood, it should be easy to verify using the example project. – Paul Young Jan 19 '14 at 18:29
  • @KamilPyc - I've added a link to a project on GitHub which demonstrates the issue. I think you're suggesting that I should pass in the blocks directly instead. I've also tried that but in case I misunderstood, it should be easy to verify using the example project. – Paul Young Jan 19 '14 at 18:30

2 Answers2

0

I checked your repo and I think now I can give you good answer.

What you are trying to do in your test is blocks comparing and currently this is not possible in Obj-c world. Even if you block from test and block created inside singleton looks identical they are not the same blocks.

You can fix this by using you fail and success block as private properties and use them in your OCMock expect. Like :

[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:sessionManager.successBlock failure:sessionManager.failureBlock];
KamilPyc
  • 376
  • 1
  • 5
  • Thanks for the help. While this is cleaner it involves changing my interface which is undesirable in this situation. – Paul Young Jan 21 '14 at 19:07
0

As others have said, blocks are compared by comparing their addresses. Two different blocks - even though they do the exact same thing - are not equal. You can still make your test more specific by actually invoking the success and failure block, and check if they behave as expected.

As far as I understand you want to test that

  • the method calls another method with two blocks, that when invoked, invoke the original block with
  • some response object as the first and nil as the second argument in case of success
  • nil as the first, an NSError* as the second argument in case of failure

Here is the test for the success case:

id responseMock = @"responseObject";
__block BOOL blockCalled = NO;

void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {
    XCTAssertNil(error, @"");
    XCTAssertEqualObjects(responseObject, responseMock, @"");
    blockCalled = YES;
};

[[[sessionManagerPartialMock expect] andDo:^(NSInvocation *invocation) {
    void (^successBlock)(NSURLSessionDataTask *, id);
    [invocation getArgument:&successBlock atIndex:4];
    successBlock((id)@"task", responseMock);
}] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];

[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];

[sessionManagerPartialMock verify];
XCTAssert(blockCalled, @"");
Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • The problem with stubbing out the blocks like this is that the tests will still pass even if the implementation doesn't call `block(responseObject, nil);` and `block(nil, error);`. – Paul Young Jan 20 '14 at 02:05
  • Just assert that your block has been called. It's not pretty, but it works. – Sebastian Jan 20 '14 at 02:24
  • Can you be more specific? – Paul Young Jan 20 '14 at 02:35
  • See my edit: Set a `BOOL` to `NO` and then set it to `YES` inside the block. When verifying your mock, also assert that the `BOOL` is `YES`. – Sebastian Jan 20 '14 at 02:43
  • That doesn't make a difference. If the actual implementation doesn't call the blocks then the tests will still pass. – Paul Young Jan 20 '14 at 03:37
  • I was mistaken - that does appear to work. I've pushed that to a branch here: https://github.com/paulyoung/OCMockExample/tree/block-stubs – Paul Young Jan 20 '14 at 03:40