How can the return value of an OCMock stub be changed?
Asked Answered
R

4

27

It seems that the first time I add andReturnValue on an OCMock stub, that return value is set in stone. For example:

id physics = [OCMockObject niceMockForClass:[DynamicPhysicsComponent class]
Entity *testEntity = [Entity entityWithPhysicsComponent:physics];
CGPoint velocity1 = CGPointMake(100, 100);
CGPoint velocity2 = CGPointZero;
[[[physics stub] andReturnValue:OCMOCK_VALUE(velocity1)] getCurrentVelocity];
[testEntity update:0.1];
[[[physics stub] andReturnValue:OCMOCK_VALUE(velocity2)] getCurrentVelocity];
[testEntity update:0.1];

The stubbed method is called in [testEntity update]. But each time the stubbed method is returning the velocity1 value, so I guess the second attempt to set the methods return value isn't honoured.

Is there a way to do do this in OCMock?

Realgar answered 14/4, 2011 at 4:22 Comment(0)
S
39

When you stub a method, you're saying it should always function in the specified way, no matter how many times it's called. The easiest way to fix this is to change stub to expect:

CGPoint velocity1 = CGPointMake(100, 100);
CGPoint velocity2 = CGPointZero;
[[[physics expect] andReturnValue:OCMOCK_VALUE(velocity1)] getCurrentVelocity];
[testEntity update:0.1];
[[[physics expect] andReturnValue:OCMOCK_VALUE(velocity2)] getCurrentVelocity];
[testEntity update:0.1];

Alternatively, if you need to stub (for example if the method might not be called at all), you can just re-create the mock:

CGPoint velocity1 = CGPointMake(100, 100);
CGPoint velocity2 = CGPointZero;
[[[physics stub] andReturnValue:OCMOCK_VALUE(velocity1)] getCurrentVelocity];
[testEntity update:0.1];
[physics verify];

physics = [OCMockObject mockForClass:[Physics class]];
[[[physics stub] andReturnValue:OCMOCK_VALUE(velocity2)] getCurrentVelocity];
[testEntity update:0.1];
[physics verify];
Stilly answered 14/4, 2011 at 16:56 Comment(4)
I see. I wasn't using expect because I had no need for verify, but using it to change the mocked method behaviour (as in your first example) works well for my case. Thanks.Realgar
In your second example, what's the purpose of verify when used with a stubbed method?Realgar
I suppose the verify might not be necessary. I was just trying to point out that you'd call verify on the mock (if appropriate to your test, say if there were other expectations) before you assign a new mock to it.Stilly
For anyone who got an error no known instance method for selector 'expect' like me, you need to cast your stub to a OCMockObject *.Incorporating
B
30

Actually when you stub you're only setting the return value in stone if you use andReturn or andReturnValue. You can use the method andDo to change the returned value whenever you want. This is a improvement over expect where you need to know how many times a method will get called. Here the code snippet to accomplish this:

__weak TestClass *weakSelf = self;
[[[physics stub] andDo:^(NSInvocation *invocation) {
    NSValue *result = [NSValue valueWithCGPoint:weakSelf.currentVelocity];
    [invocation setReturnValue:&result];
}] getCurrentVelocity];
Brett answered 2/11, 2012 at 14:20 Comment(1)
I like this answer better because you're not enforcing how many times the stubbed method must be called (as -expect) would do.Cnidus
D
3

While I think CipherCom has the correct answer I find myself preferring to create a helper class for returning various values. I've had issues with NSInvocation in the past.

@interface TestHelper : NSObject
@property (nonatomic, assign) CGPoint velocity;
- (CGPoint)getCurrentVelocity;
@end

@implementation TestHelper
- (CGPoint)getCurrentVelocity
{
    return self.velocity;
}
@end

Then in my test class I'd have a private member variable for TestHelper and in setUp method I'd do:

self.testHelper = [TestHelper new];

[[[physics stub] andCall:@selector(getCurrentVelocity) onObject:self.testHelper]
                 getCurrentVelocity]; 

That way in each of my tests I could set the velocity to what I want for the test.

self.testHelper.velocity = CGPointMake(100, 200);

Dismay answered 16/3, 2015 at 16:59 Comment(0)
T
1

Looks like neither andReturn/andReturnValue/andDo doesn't get overriden when called multiple times. My workaround was to add a property to the test class and use that to control what the mocked object should return. For example in case of an isAvailable property on a mocked object, my code would look like:

@interface MyTest: XCTestCase 
@property BOOL stubbedIsAvailable;
@end

@implementation MyTest

- (void)setUp {
    [OCMStub([myMockedObject isAvailable]) andDo:^(NSInvocation invocation) {
        BOOL retVal = self.stubbedIsAvailable;
        [invocation setReturnValue:&retVal];
    }
}

- (void)testBehaviourWhenIsAvailable {
    self.stubbedIsAvailable = YES;
    // test the unit
}

- (void)testBehaviourWhenIsNotAvailable {
    self.stubbedIsAvailable = NOT;
    // test the unit
}
Tortoiseshell answered 20/1, 2016 at 14:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.