It seems that the first time I add andReturnValue on an OCMock stub, that return value is set in stone. For exampl开发者_Go百科e:
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?
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];
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];
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);
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
}
精彩评论