How to partially mock an object inside legacy code with OCMock?

心已入冬 提交于 2020-01-06 14:51:11

问题


I would like to accomplish what also is described here, i.e create mocks inside legacy code. However I require partial instead of nice or strict mocks.

For example, consider leaderboards that behave exactly like GKLeaderbaord except for implementing a stubbed version of loadScoresWithCompletionHandler:.

I've tried this code inside an XCTestCase but it currently fails at runtime in the indicated line: OCMInvocationMatcher raises an EXC_BAD_ACCESS error. Perhaps there is some infinite recursion going on.

id leaderboardMock = OCMClassMock(GKLeaderboard.class);
OCMStub([leaderboardMock alloc])
    .andReturn(OCMPartialMock([GKLeaderboard alloc]));
OCMStub([leaderboardMock loadScoresWithCompletionHandler: [OCMArg any]])
    .andDo(^(NSInvocation *invocation) { /* ... */ });

// these parts normally nested inside legacy code

GKLeaderboard *leaderboard = /* raises EXC_BAD_ACCESS */
    [[GKLeaderboard alloc] initWithPlayers: @[ GKLocalPlayer.localPlayer ]];
leaderboard.identifier = @"Test";

[leaderboard loadScoresWithCompletionHandler: nil /* ... */ ];

What am I doing wrong and is this even possible for partial mockups?

UPDATE I can by now see how the indicated line might (quite obviously) cause an infinite recursion, but don't yet know how to avoid (or break) it.

UPDATE I've also had no success with an attempt of bringing in an dedicated class with OCMStub([leaderboardMock alloc]).andReturn([LeaderboardMock alloc]) (nor with OCMStub([leaderboardMock initWithPlayers: [OCMArg any]]).andReturn([[LeaderboardMock alloc] initWithPlayers:nil])). Perhaps OCMock does its magic at the level of init (the documentation says: "it is not possible to stub the init method, because that is implemented by the mock itself") hence such an attempt the level of alloc (or initWithPlayers:) cannot have its desired effect.


回答1:


Not sure I follow what you are trying to do. It seems like a misunderstanding. Does the following not work for you?

GKLeaderboard *leaderboard = [[GKLeaderboard alloc] initWithPlayers: ... ];
id leaderboardMock = OCMPartialMock(leaderboard);
OCMStub([leaderboarMock loadScoresWithCompletionHandler: ...]);

You can use the normal object without restrictions. You can use the partial mock created for the object to manipulate the actual instance in leaderboard. That's the beauty of partial mocks.

UPDATE: If the object creation is not under your control, you can try the following:

GKLeaderboard *leaderboard = [[GKLeaderboard alloc] initWithPlayers: ... ];
id leaderboardMock = OCMPartialMock(leaderboard);

OCMStub([leaderboardMock alloc]).andReturn(leaderboardMock);
OCMStub([leaderboardMock initWithPlayers:[OCMArg any]).andReturn(leaderboard);

OCMStub([leaderboarMock loadScoresWithCompletionHandler: ...]);



回答2:


I have by now concluded that method swizzling would be a possible choice.

A replacement method could e.g. generate a partial mockup from within the context of legacy code and hence introduce a partial mock in that context without requiring changes to legacy APIs.




回答3:


you should not use following line, it will mock your entire class and none of real object will get called.

OCMClassMock(GKLeaderboard.class)



来源:https://stackoverflow.com/questions/28240858/how-to-partially-mock-an-object-inside-legacy-code-with-ocmock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!