I've been mocking out accesses to the Parse Data backend and run into trouble with OCMock.
The primary data access mechanism for accessing the backend is through a Parse
PFQuery
object, constructed with [PFQuery queryWithClassName:@"ClassName"]
.
This is naturally a good choice as a test seam.
I want to use Partial mocks in this case for reasons I will not go into in this post.
Instead of returning a real PFQuery, I can rig this class method to return a mock object like so:
id queryMock = OCMClassMock([PFQuery class]);
OCMStub([queryMock queryWithClassName:className]).andDo(^(NSInvocation *invocation){
id mockQuery = OC...;
NSLog(@">> CREATED MOCK QUERY: %@, %p", mockQuery, &mockQuery);
[invocation setReturnValue:&mockQuery];
});
... with the mock query's set up like so:
PFQuery *query = [[PFQuery alloc] initWithClassName:className];
id mockQuery = OCMPartialMock(query);
OCMStub([mockQuery findObjectsInBackgroundWithBlock:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
typedef void (^FindObjectsBlock)(NSArray *, NSError *error);
FindObjectsBlock callback;
[invocation getArgument:&callback atIndex:2];
NSArray *results = evaluateQueryResultArrayFromDataArray(query, objects);
callback(results, nil);
});
But to mimic the behaviour of the SDK, I would like to be able to call this method multiple times, and I expect distinct mock objects to be produced. This is important.
I chose to use OCMock's class method mocking capabilities. I found that if I invoke the class method twice, the mocking behaviour only applies to the first time. The second time, mocking is ineffective.
NSLog(@"BEFORE first");
PFQuery *firstQuery = [PFQuery queryWithClassName:@"THINGS"];
NSLog(@"AFTER first");
// Configure first query...
NSLog(@"BEFORE second");
PFQuery *secondQuery = [PFQuery queryWithClassName:@"THINGS"];
NSLog(@"AFTER second");
// Configure second query...
expect(secondQuery).toNot.equal(firstQuery); // OK
[firstQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(@"THIS IS EXECUTED CORRECTLY %@", objects); // OK; returns array as expected
}];
// The following query FAILS because the real underlying method is called.
[secondQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(@"THIS DOESN'T EXECUTE RIGHT %@", objects); // FAILS
}];
The trace clearly shows the .andDo()
behaviour is executed once only.
2014-10-21 16:23:53.186 xctest[18380:209978] BEFORE first
2014-10-21 16:23:53.188 xctest[18380:209978] >> CREATED MOCK QUERY: OCPartialMockObject(PFQuery), 0x7fff5e74d980
2014-10-21 16:23:53.188 xctest[18380:209978] AFTER first
2014-10-21 16:23:53.188 xctest[18380:209978] BEFORE second
2014-10-21 16:23:53.188 xctest[18380:209978] AFTER second
Why is this?
[... times:3]
? – fatuhoku