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?

Cris
  • 1,939
  • 3
  • 23
  • 37

4 Answers4

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];
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • 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. – Cris Apr 14 '11 at 20:27
  • 2
    In your second example, what's the purpose of `verify` when used with a stubbed method? – Cris Apr 14 '11 at 20:28
  • 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. – Christopher Pickslay Apr 14 '11 at 23:00
  • For anyone who got an error `no known instance method for selector 'expect'` like me, you need to cast your stub to a `OCMockObject *`. – Liron Yahdav Apr 11 '17 at 23:12
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];
Patrik
  • 1,117
  • 12
  • 21
  • 3
    I like this answer better because you're not enforcing how many times the stubbed method must be called (as -expect) would do. – George Dec 29 '13 at 23:17
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);

Mr Rogers
  • 6,091
  • 2
  • 32
  • 34
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
}
Cristik
  • 30,989
  • 25
  • 91
  • 127