32

I'm learning how to use OCMock to test my iPhone's project and I have this scenario: a HeightMap class with a getHeightAtX:andY: method, and a Render class using HeightMap. I'm trying to unit test Render using some HeightMap mocks. This works:

id mock = [OCMockObject mockForClass:[Chunk class]];
int h = 0;
[[[mock stub] andReturnValue:OCMOCK_VALUE(h)] getHeightAtX:0 andY:0];

Of course, works only for x=0 and y=0. I want to test using a "flat" height map. This means I need to do something like this:

id chunk = [OCMockObject mockForClass:[Chunk class]];
int h = 0;
[[[chunk stub] andReturnValue:OCMOCK_VALUE(h)] getHeightAtX:[OCMArg any] andY:[OCMArg any]];

But this raises two compilation warnings:

warning: passing argument 1 of 'getHeightAtX:andY:' makes integer from pointer without a cast

and a runtime error:

unexpected method invoked: 'getHeightAtX:0 andY:0 stubbed: getHeightAtX:15545040 andY:15545024'

What am I missing? I found no way to pass a anyValue to this mock.

Regexident
  • 29,441
  • 10
  • 93
  • 100
Eduardo Costa
  • 1,974
  • 1
  • 16
  • 22
  • It's possible to edit OCMock to do this, if it's worth your time: http://stackoverflow.com/questions/16916115/ocmock-passing-any-cgsize/16923742#16923742 – Ben Flynn Jun 04 '13 at 17:22

6 Answers6

50

It's been awhile since this question has been asked but I ran into this issue myself and couldn't find a solution anywhere. OCMock now supports ignoringNonObjectArgs so an example of an expect would be

[[[mockObject expect] ignoringNonObjectArgs] someMethodWithPrimitiveArgument:5];

the 5 doesn't actually do anything, just a filler value

Andrew Park
  • 1,489
  • 1
  • 17
  • 26
  • 3
    Also a warning, if you have a method you're stubbing and using the `ignoringNonObjectArgs`, if you have a block AFTER the primitive argument and you're stubbing it with `[OCMarg checkWithBlock:]`, that block won't be evaluated (invoked) – Andrew Park Nov 08 '13 at 18:38
  • 2
    This is now the correct answer to OP's question. Thank you for pointing this out! I hadn't seen this was added to OCMock. – Christopher Pickslay Dec 05 '13 at 07:03
  • +1 Given that it has weird side-effects I think the warning was a good call. A partial solution! – fatuhoku Apr 06 '14 at 22:41
  • Is there also a macro syntax similar to `OCMExpect` to support this? – Drux Feb 17 '15 at 17:24
18

OCMock doesn't currently support loose matching of primitive arguments. There's a discussion about potential changes to support this on the OCMock forums, though it seems to have stalled.

The only solution I've found is to structure my tests in such a way that I know the primitive values that will be passed in, though it's far from ideal.

Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • has anything changed since you made this post about this topic? – abbood Oct 11 '13 at 00:38
  • I believe you can now do this with [OCMArg setToValue:OCMOCK_VALUE(some_primitive)]. It basically wraps the primitive with an NSValue. At least, the "features" page shows this as an example: http://ocmock.org/features/. – stuckj Nov 27 '13 at 20:36
  • 1
    @stuckj that doesn't solve OP's problem. `setToValue` allows you to stub a method that takes a pointer argument. Generally the method would assign some value to that pointer. `setToValue` allows you to assign a value when the stub intercepts the call. OP is trying to do a loose match of a primitive argument. @Andrew_Park has the (now) correct answer, to use `ignoringNonObjectArgs`. – Christopher Pickslay Dec 05 '13 at 07:02
2

Use OCMockito instead.

It supports primitive argument matching.

For instance, in your case:

id chunk = mock([Chunk class]);
[[given([chunk getHeightAtX:0]) withMatcher:anything() forArgument:0] willReturnInt:0];
yonix
  • 11,665
  • 7
  • 34
  • 52
1

In addition to Andrew Park answer you could make it a little bit more general and nice looking:

#define OCMStubIgnoringNonObjectArgs(invocation) \
({ \
    _OCMSilenceWarnings( \
        [OCMMacroState beginStubMacro]; \
        [[[OCMMacroState globalState] recorder] ignoringNonObjectArgs]; \
        invocation; \
        [OCMMacroState endStubMacro]; \
    ); \
})

The you can use it like that:

OCMStubIgnoringNonObjectArgs(someMethodParam:0 param2:0).andDo(someBlock)

You can do the same for expecting. This case is for stubbing as topic starter request. It was tested with OCMock 3.1.1.

Alex
  • 378
  • 1
  • 14
1

You could do like this: id chunk = OCMClassMock([Chunk class]) OCMStub([chunk ignoringNonObjectArgs] getHeightAtX:0 andY:0]])

Readmore at: http://ocmock.org/reference/#argument-constraints

Tan Vu
  • 11
  • 1
  • Please provide a better description for your solution. Links are great, but they can break someday. https://stackoverflow.com/help/how-to-answer – sɐunıɔןɐqɐp May 04 '18 at 08:04
0

Despite being fairly hacky, the approach of using expectations to store the passed block to call later in the test code has worked for me:

- (void)testVerifyPrimitiveBlockArgument
{
    // mock object that would call the block in production
    id mockOtherObject = OCMClassMock([OtherObject class]);

    // pass the block calling object to the test object
    Object *objectUnderTest = [[Object new] initWithOtherObject:mockOtherObject];

    // store the block when the method is called to use later
    __block void (^completionBlock)(NSUInteger value) = nil;
    OCMExpect([mockOtherObject doSomethingWithCompletion:[OCMArg checkWithBlock:^BOOL(id value) { completionBlock = value; return YES; }]]);

    // call the method that's being tested
    [objectUnderTest doThingThatCallsBlockOnOtherObject];

    // once the expected method has been called from `doThingThatCallsBlockOnOtherObject`, continue
    OCMVerifyAllWithDelay(mockOtherObject, 0.5);
    // simulate callback from mockOtherObject with primitive value, can be done on the main or background queue
    completionBlock(45);
}
Matt Robinson
  • 799
  • 7
  • 22