John Gallagher
John Gallagher

Reputation: 6278

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?

Upvotes: 3

Views: 911

Answers (5)

kcstricks
kcstricks

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

dariaa
dariaa

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

e1985
e1985

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

Dave Dribin
Dave Dribin

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

Abizern
Abizern

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

Related Questions