11

I have a method I would like to test with OCMock but not sure how to do that. I need to mock ExtClass which isn't defined as part of my code (external library):

+(NSString *)foo:(NSString *)param
{
    ExtClass *ext = [[ExtClass alloc] initWithParam:param];
    if ([ext someMethod])
        return @"A";
    else
        return @"B";
}

Thanks in advance!

soguy
  • 265
  • 1
  • 3
  • 10
  • 1
    Your method return type is BOOL and then you are returning a string. Could you edit and change any of those things? I guess you want to return a string, but I am not sure. – e1985 Aug 29 '13 at 13:18

1 Answers1

29

OCMock 2

id mock = [OCMockObject mockForClass:[ExtClass class]];
// We stub someMethod
BOOL returnedValue = YES;
[[[mock stub] andReturnValue:OCMOCK_VALUE(returnedValue)] someMethod];

// Here we stub the alloc class method **
[[[mock stub] andReturn:mock] alloc];
// And we stub initWithParam: passing the param we will pass to the method to test
NSString *param = @"someParam";
[[[mock stub] andReturn:mock] initWithParam:param];

// Here we call the method to test and we would do an assertion of its returned value...
[YourClassToTest foo:param];

OCMock3

// Parameter
NSURL *url = [NSURL URLWithString:@"http://testURL.com"];

// Set up the class to mock `alloc` and `init...`
id mockController = OCMClassMock([WebAuthViewController class]);
OCMStub([mockController alloc]).andReturn(mockController);
OCMStub([mockController initWithAuthenticationToken:OCMOCK_ANY authConfig:OCMOCK_ANY]).andReturn(mockController);

// Expect the method that needs to be called correctly
OCMExpect([mockController handleAuthResponseWithURL:url]);

// Call the method which does the work
[self.myClassInstance authStarted];

OCMVerifyAll(mockController);

Notes

Ensure that in both cases you stub two methods (alloc and the init... method). Also, make sure that both stubbing calls are made on the instance of the class mock (not the class itself).

Docs: Class methods section in the OCMock features

Alternatives

This (strange)solution may be useful in case you want to test legacy code that due to whatever reason you cannot refactor. However, if you can modify the code you should refactor it and get an ExtClass object as a parameter, not a string, delegating the creation of ExtClass out of that method. Your production and test code would be simpler and clearer, specially in a more complex real life case, not in this simple example.

Steven Hepting
  • 12,394
  • 8
  • 40
  • 50
e1985
  • 6,239
  • 1
  • 24
  • 39
  • Is it possible to return a distinct object/mock for each `alloc`? This seems necessary for maintaining several instances with different values of `param` (i.e. `ExtClass` with normal/non-singleton semantics). See related question [here](http://stackoverflow.com/questions/28240858/how-to-partially-mock-an-object-inside-legacy-code-with-ocmock). Thx. – Drux Jan 31 '15 at 10:33
  • This doesn't seem to be working with OCMock version 3, but I could be doing it wrong. – Steven Hepting Mar 31 '15 at 17:50
  • Thanks. U made my day :) – larva Jun 09 '15 at 04:12
  • The key piece here is that the `stub` method calls need to happen *twice*, and in both cases they happen on the class mock `instance`. – Steven Hepting May 02 '16 at 22:55
  • I don't think this answer should be accepted, it doesn't work in OCMock 3, please check here: http://stackoverflow.com/questions/37654226/stub-someclazz-alloc-init-not-work-but-accepted-answer-says-it-should-work – Leem.fin Jun 06 '16 at 17:19
  • 1
    This *does* work in OCMock 3. It's subtle. You cannot mock the `init` method because that's implemented and used by the mock object. However, you can mock other init methods, `initWithAuthenticationToken:authConfig:` in this case, because these are not implemented by the mock object. – Erik Doernenburg Jun 07 '16 at 07:34