Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Nested Error Handling with OCMock

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?

like image 707
John Gallagher Avatar asked Apr 13 '26 04:04

John Gallagher


2 Answers

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]];
}
like image 197
dariaa Avatar answered Apr 16 '26 09:04

dariaa


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?

like image 36
Dave Dribin Avatar answered Apr 16 '26 08:04

Dave Dribin