如何使用 OCMock 部分模拟遗留代码中的对象?

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

我想完成here, i.e create mocks inside legacy code. However I require partial instead of nice or strict模拟所描述的内容。

例如,考虑行为与 GKLeaderbaord 完全相同的排行榜,除了实施 loadScoresWithCompletionHandler:.

的存根版本

我已经在 XCTestCase 中尝试过此代码,但它目前在指定行的运行时失败:OCMInvocationMatcher 引发 EXC_BAD_ACCESS 错误。也许有一些无限递归正在进行。

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 /* ... */ ];

我做错了什么,这甚至可以用于部分模型吗?

UPDATE 我现在可以看到指示的行可能(很明显)如何导致无限递归,但还不知道如何避免(或中断)它。

UPDATE 我也没有成功尝试引入专用 class 和 OCMStub([leaderboardMock alloc]).andReturn([LeaderboardMock alloc])(也没有 OCMStub([leaderboardMock initWithPlayers: [OCMArg any]]).andReturn([[LeaderboardMock alloc] initWithPlayers:nil]) ).也许 OCMockinitdocumentation 说:"it is not possible to stub the init method, because that is implemented by the mock itself")的水平上发挥了它的魔力,因此这种尝试达到了 alloc(或 [=22)的水平=]) 无法达到预期的效果。

不确定我是否遵循了您的意图。这似乎是一个误会。以下内容对您不起作用吗?

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

您可以无限制地使用普通对象。您可以使用为对象创建的部分模拟来操作 leaderboard 中的实际实例。这就是部分模拟的美妙之处。

更新:如果对象创建不在你的控制之下,你可以尝试以下方法:

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

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

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

我现在得出结论,method swizzling 是一个可能的选择。

替换方法可以是,例如从遗留代码的上下文中生成部分模型,因此在该上下文中引入部分模型,而无需更改遗留 API。

你不应该使用下面的行,它会模拟你的整个 class 和真实对象的 none 将被调用。

OCMClassMock(GKLeaderboard.class)