33

Assume a method signature such as the following:

- (void)theMethod:(void(^)(BOOL completed))completionBlock;

I would like to mock this method signature to ensure the method is called, and just call the completion block. I see from other posts like this one that I can mock the method call and accept any block, but not run the block. I also know there is a andDo method that I might be able to use, but I can't figure out how to pass a block in and run it.

Any ideas?

Thanks.

Community
  • 1
  • 1
Mike
  • 1,112
  • 1
  • 13
  • 20

5 Answers5

34

You can use [[mock stub] andDo:] like this to pass another block that gets called when your mocked method is called:

void (^proxyBlock)(NSInvocation *) = ^(NSInvocation *invocation) {
     void (^passedBlock)( BOOL );
     [invocation getArgument: &passedBlock atIndex: 2];
};
[[[mock stub] andDo: proxyBlock] theMethod:[OCMArg any]];

The block gets a NSInvocation instance from which you can query all the used arguments. Note that the first argument is at index 2 since you have self and _cmd at the indices 0 and 1.

Sven
  • 22,475
  • 4
  • 52
  • 71
17

EDIT 2: Use https://stackoverflow.com/a/32945785/637641 instead.

Using andDo: is perfectly fine, but personally I prefer [OCMArg checkWithBlock:].

[[mock expect] theMethod:[OCMArg checkWithBlock:^BOOL(id param)
{
    void (^passedBlock)( BOOL ) = param;
    // Normally I set some expectations here and then call the block.
    return YES;
}]];

// Code to test

[mock verify];

You can use also [mock stub] but I prefer to verify that theMethod is called.

EDIT 1

OCMock 3 version:

OCMExpect([mock theMethod:[OCMArg checkWithBlock:^BOOL(void (^passedBlock)(BOOL))
{
    // call the block...
    return YES;
}]]);

// Code to test

OCMVerify(mock);
Community
  • 1
  • 1
e1985
  • 6,239
  • 1
  • 24
  • 39
  • 3
    There are two problems with using checkWithBlock for this purpose. One is that the current version of OCMock will stop checking object parameters if `ignoringNonObjectArgs` is true and a non-object param comes before the block param. The second problem is if you have an argument constraint fail after your checkWithBlock call, your completion block will be mistakenly called. The only reliable method for calling a block is to use `andDo:`. This is too bad, because `checkWithBlock` avoids annoying parameter hand-counting for NSInvocation. – phatmann Nov 26 '13 at 22:42
12

This is now supported in OCMock 3.2. You can use [OCMArg invokeBlock] and [OCMArg invokeBlockWithArgs:...] to invoke the block passed as an argument to a stubbed method.

Erik Doernenburg
  • 2,933
  • 18
  • 21
  • 1
    It would be very useful if you could provide an example of how you'd use them. – Francesco Puglisi Nov 06 '15 at 14:38
  • 1
    @singingAtom check out [the OCMock tests](https://github.com/erikdoe/ocmock/blob/master/Source/OCMockTests/OCMockObjectTests.m#L618-L711). I also put some examples on Github [here](https://github.com/SteveFortune/ocmock-block-tests) - these are a bit out of date though, e.g. `OCMDefault` has changed to `[OCMArg defaultValue]`. – sf13579 Nov 07 '15 at 16:05
  • This is by far the best answer, as it avoids the headaches associated with `NSInvocation`. – Chris McGrath Oct 07 '16 at 20:09
  • 2
    Example Code: `OCMStub([theSubject theMethod:([OCMArg invokeBlockWithArgs:@YES, nil])]);` Or if you have a completionBlock with success flag and NSError you could do this: `OCMStub([theSubject theMethod:([OCMArg invokeBlockWithArgs:@YES, [NSNull null], nil])]);` – cornr Feb 05 '17 at 13:09
9

Using andDo: blocks is sometimes required but for most cases you can use [OCMArg invokeBlock] or [OCMArg invokeBlockWithArgs:].

In your example you can do the following
If you don't care about the arguments:

// Call block with default arguments.
OCMStub([mock theMethod:[OCMArg invokeBlock]];

If you want to send specific arguments:

// Call block with YES.
OCMStub([mock theMethod:([OCMArg invokeBlockWithArgs:@YES, nil])];

Note the nil termination since you can pass multiple arguments to this method. In addition the entire expression must be wrapped in parentheses.

You can read more about it in the OCMock documentation.

Barak Yoresh
  • 279
  • 2
  • 4
5

This is Sven's answer updated for OCMock 3.

OCMStub([myMock myMethodWithMyBlock:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
    void (^passedBlock)(BOOL myFirstArgument, NSError *mySecondArgument);
    [invocation getArgument: &passedBlock atIndex: 2];
    passedBlock(YES, nil);
});
balkoth
  • 698
  • 7
  • 11