Passing primitives to an OCMock's stub
Asked Answered
H

6

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.

Horizontal answered 12/6, 2011 at 23:2 Comment(1)
It's possible to edit OCMock to do this, if it's worth your time: https://mcmap.net/q/454297/-ocmock-passing-any-cgsize/…Polarity
W
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.

Wiesbaden answered 13/6, 2011 at 14:47 Comment(3)
has anything changed since you made this post about this topic?Jabberwocky
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: ocmock.org/features.Catercornered
@Catercornered 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.Wiesbaden
C
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

Cyanite answered 8/11, 2013 at 1:0 Comment(4)
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)Cyanite
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.Wiesbaden
+1 Given that it has weird side-effects I think the warning was a good call. A partial solution!Dowland
Is there also a macro syntax similar to OCMExpect to support this?Salivate
W
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.

Wiesbaden answered 13/6, 2011 at 14:47 Comment(3)
has anything changed since you made this post about this topic?Jabberwocky
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: ocmock.org/features.Catercornered
@Catercornered 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.Wiesbaden
M
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];
Majka answered 23/5, 2013 at 7:35 Comment(1)
Ach, I like OCMockito, but it's all very much Y U NO PARTIAL MOCKING!?Dowland
J
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.

Jupon answered 18/12, 2014 at 0:31 Comment(2)
Um, what object is someMethodParam:0 param2:0 being invoked on?Dowland
object, which you're actually mockingJupon
E
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

Eba answered 4/5, 2018 at 7:57 Comment(1)
Please provide a better description for your solution. Links are great, but they can break someday. stackoverflow.com/help/how-to-answerPendentive
S
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);
}
Sisterinlaw answered 4/5, 2017 at 23:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.