Reputation: 6278
I'd really appreciate some advice around testing for errors using OCMock.
Method Under Test
It grabs a process using an instance of the AppScriptController class.
@implementation ProcessGrabber
-(void)grabProcess {
NSError *error = nil;
NSString *processName = [appScriptController processName:ProcessRef
error:&error];
if(error == nil) {
NSNumber *processID = [appScriptController processID:ProcessRef
error:&error];
if(error == nil) {
... More operations...
}
}
if(error) {
[NSApp raiseError:error];
}
}
@end
Method to Mock
The AppScriptController class interacts with the system, so I want to mock it out.
@implementation AppScriptController
-(NSString *)processName:(SERef *)theProcessRef error:(NSError **)theError {
return [[theProcessRef name] error:error];
}
-(NSNumber *)processID:(SERef *)theProcessRef error:(NSError **)theError {
return [[theProcessRef name] error:error];
}
The Test
-(void)testGrabProcess {
NSError *error = nil;
OCMockObject *mock = [OCMockObject mockForClass:[AppScriptController class]];
[[[mock expect] andReturn:@"Process Name"] processName:nil error:&error];
// ... Somehow inject an error here...
[[[mock expect] andReturn:34] processID:nil error:&error];
}
The Problem
I want to check the nested error handling code works correctly. So I want to be able to inject a specific error code at a specific point in the method.
Examples
Simulate an error in the process name grabbing. I'd test that the process ID grabbing doesn't get called.
Simulate an error only in the process ID grabbing. I'd test that the process Name grabbing is run, but no operations after process ID are executed.
No matter where the error occurs, test that the correct error is raised.
What I've tried
The obvious thing to try is to set the error where I've put "...Somehow inject an error in here...". I didn't expect this to work, and it didn't.
I've searched on Google for answers and I've thought about trying to somehow wrap the error in a seperate class, but I can't see how this would help.
I've thought about this for hours, but I'm still no nearer to a solution.
Can anyone help?
Upvotes: 3
Views: 911
Reputation: 1579
For those looking for OCMock 3 syntax:
AppScriptController *controller = OCMClassMock([AppScriptController class]);
OCMStub([controller processName:[OCMArg any] error:[OCMArg setTo:nil]]).andDo(^(NSInvocation *invocation) {
NSError * __autoreleasing *err = nil;
[invocation getArgument:&err atIndex:3];
*err = [NSError errorWithDomain:@"MyDomain" code:123 userInfo:nil];
});
Upvotes: 0
Reputation: 6385
In case someone else is still looking for this, there is in fact a rather simple way to do it with OCMock
:
- (void)testGrabProcess {
OCMockObject *mock = [OCMockObject mockForClass:[AppScriptController class]];
[[[[mock expect] andDo:^(NSInvocation *invocation) {
NSError * __autoreleasing *anError = nil;
[invocation getArgument:&anError atIndex:3];
*anError = [NSError errorWithDomain:@"MYDomain" code:2323 userInfo:nil];
}] andReturn:@"Process Name"] processName:nil error:&error];
[[[mock expect] andReturnValue:@34] processID:nil error:(BECloudPlayerDevice *__autoreleasing *)[OCMArg anyPointer]];
}
Upvotes: 3
Reputation: 6279
I solved this problem as follows:
Having a method in MyClass like this:
- (BOOL) myMethod:(id)someParameter error:(NSError **)error;
In the test I do this:
id myMock = [OCMockObject mockForClass:[MyClass class]];
[[myMock expect] myMethod:someParameter error:(NSError *__autoreleasing *)[OCMArg anyPointer]];
And this way I avoid creating a custom mock subclass of MyClass.
Upvotes: 0
Reputation: 5662
I'd love to seen an actual test method with real test code, as I feel something is getting lost in the the simplification of the question. That said, perhaps OCMock isn't the best tool for the job, here? I'm not so sure it handles NSError **
parameters very well (I've never tried). Perhaps you'd be better served by using a custom test stub, rather than a generic mock object?
Upvotes: 2
Reputation: 150715
You could create another test for the case of returning nil;
-(void)testGrabProcessError {
NSError *error = //create the error code you want to test here.;
OCMockObject *mock = [OCMockObject mockForClass:[AppScriptController class]];
[[[mock expect] andReturn:nil] processName:nil error:&error];
...
}
Upvotes: 0