stub [[SomeClazz alloc] init] not work but accepted answer says it should work
Asked Answered
I

1

3

My function under test is very simple:

@implementation MyHandler
...
-(void) processData {
  DataService *service = [[DataService alloc] init];
  NSDictionary *data = [service getData];
  [self handleData:data];
}

@end

I use OCMock 3 to unit test it.

I need to stub the [[DataService alloc] init] to return a mocked instance, I tried the answer from this question (which is an accepted answer) to stub [[SomeClazz alloc] init]:

// Stub 'alloc init' to return mocked DataService instance,
// exactly the same way as the accepted answer told
id DataServiceMock = OCMClassMock([DataService class]);
OCMStub([DataServiceMock alloc]).andReturn(DataServiceMock);
OCMStub([DataServiceMock init]).andReturn(DataServiceMock);

// run function under test
[MyHandlerPartialMock processData];

// verify [service getData] is invoked
OCMVerify([dataServiceMock getData]);

I have set break point in function under test, I am sure [service getData] is called when run unit test, but my above test code (OCMVerify) fails. Why?

Is it because the function under test is not using my mocked DataService? But the answer accepted in that question tells it should work. I get confused now...

I want to know how to stub [[SomeClazz alloc] init] to return mocked instance with OCMock?

Interfluve answered 6/6, 2016 at 9:45 Comment(2)
I would encourage you to find a different design. The documentation says this about trying to stub alloc/init: "If you find yourself doing this a lot, please consider the dependency injection pattern. It is not possible to stub the init method, because that is implemented by the mock itself. "Dis
Did my answer work for you?Reno
R
2

You cannot mock init as it is implemented by the mock object itself. The reason that mocking init works in the answer you linked is because it is a custom init method. If you do not want to use dependency injection, you will have to write a custom init method for DataService that you can mock.

In your implementation add a custom init method:

// DataService.m
...
- (id) initForTest
{
    self = [super init];
    if (self) {
        // custom initialization here if necessary, otherwise leave blank
    }

    return self;
}
...

Then update MyHandler implementation to call this initForTest:

@implementation MyHandler
...
-(void) processData {
  DataService *service = [[DataService alloc] initForTest];
  NSDictionary *data = [service getData];
  [self handleData:data];
}

@end

And finally update your test to stub initForTest:

id DataServiceMock = OCMClassMock([DataService class]);
OCMStub([DataServiceMock alloc]).andReturn(DataServiceMock);
OCMStub([DataServiceMock initForTest]).andReturn(DataServiceMock);

// run function under test
[MyHandlerPartialMock processData];

// verify [service getData] is invoked
OCMVerify([dataServiceMock getData]);

Feel free to rename initForTest, so long as it isn't called init.

Reno answered 6/6, 2016 at 19:0 Comment(2)
I'm a little confused by this solution, since the second OCMStub() seems to be returning a class mock object for an init function that is expecting to return an instance method. I tried this technique for a similar problem and see an exception where it is failing to use the object returned from init, which is of type 'OCClassMockObject'.Evaevacuant
How are you using the class mock object? It's tough to say what could be going on without seeing the codeReno

© 2022 - 2024 — McMap. All rights reserved.