views:

123

answers:

2

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?

+1  A: 

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];
  ...
}
Abizern
Thanks for your response. Yes, that's the obvious way of doing it when you have one mock object. I've not articulated my problem correctly in my question - sorry about that. My problem is that I have 3 or 4 mocks in the test, all of which are passed the error. I need to inject the error **between** mocks, so that I can test nested error handling.I'll update my question accordingly.
John Gallagher
I thought it was a bit obvious.
Abizern
+2  A: 

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?

Dave Dribin
Thanks for your answer. I don't want to post the code as it's pretty poor and convoluted right now - too much so for other people to read.However, I've tried to implement my own custom test mock, and it was far easier than I thought it would be. That's solved the problem for me - my custom mock is now working like a dream, so thanks for your suggestion.
John Gallagher