How to mock an object with OCMock which isn't passed as a parameter to method?
Asked Answered
E

1

12

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!

Effeminize answered 29/8, 2013 at 6:21 Comment(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.Gestapo
G
30

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.

Gestapo answered 29/8, 2013 at 13:42 Comment(5)
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. Thx.Novellanovello
This doesn't seem to be working with OCMock version 3, but I could be doing it wrong.Scholasticism
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.Scholasticism
I don't think this answer should be accepted, it doesn't work in OCMock 3, please check here: #37654726Structural
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.Canticle

© 2022 - 2024 — McMap. All rights reserved.