用于模拟对象的 OCMock 无法识别的选择器
Posted
技术标签:
【中文标题】用于模拟对象的 OCMock 无法识别的选择器【英文标题】:OCMock Unrecognized Selector for mocked object 【发布时间】:2013-12-05 01:12:36 【问题描述】:我有一个似乎与 OCMockObject PartialMock 相关的错误。 当模拟一个对象并存根一个方法时,我得到这个无法识别的选择器错误,我很确定这是一个订单或强制转换问题。 这是我的测试
STV_StreamServer *server = [NSEntityDescription insertNewObjectForEntityForName:@"STV_StreamServer"inManagedObjectContext:context];
id mockServer = [OCMockObject partialMockForObject:server];
[[[mockServer stub] andReturnValue:@YES] localURLPresent];
[[[mockServer stub] andReturnValue:@NO] remoteURLPresent];
id mockSUT = [OCMockObject partialMockForObject:sut];
[[[mockSUT stub] andReturnValue:@YES] canLiveStream:nil];
sut.streamServer = mockServer;
NSError *err = [mockSUT checkStreamingPlayabilityForUser:[self getUser:NO]];
XCTAssertNil(err, @"An error occured when basic user tried local playback");
sut 是一个 STV_MediaServer。我得到的错误是[STV_MediaServer-0xb39aba0-407898154.181220 setStreamServer:]: unrecognized selector sent to instance 0xb39aba0
。所以首先我看到对象类型似乎是错误的,因为它现在包含看起来像内存位置的东西。当我嘲笑我的 sut 时会发生这种情况。我确定这是订单问题。谷歌搜索了几个小时。
【问题讨论】:
离题评论:在 ObjC 中,通知 abour 错误的约定是返回 YES/NO 并传递一个 NSError ** 参数,该参数将在发生错误时设置。 我认为这是异步方法的约定。如果您查看使用的一些低级 c++ 方法,它们会像许多核心音频方法一样返回 NSErrors。你有链接到苹果说用哪种方法做的页面吗?因为我一直在寻找更合规的代码 我没有任何指向任何 Apple 页面的链接,但我在 Cocoa API 中找不到任何返回 NSError 的方法。例如,考虑一下 NSManagedObjectContext 中的 save: 方法。 我知道,如果你设置一个异常断点,应用会在 sut.streamServer = mockServer; 中停止,对吧? 没有应用程序在 sut 中的私有方法中停止,该方法是从检查 self.streamServer 的checkStreamingPlayabilityForUser
调用的
【参考方案1】:
我没有重现您的问题,但我认为不可能在托管对象中存根属性(使用 @dynamic 声明的 CoreData 属性)。
对于您展示的这种情况,您可以简单地将属性设置为您想要的值 - 这里不需要存根。
【讨论】:
我正在存根方法而不是属性,但错误是测试的方法访问属性。如果您查看错误,您可以看到对象类型已被 ocmock 更改 您正在对运行时生成的方法进行存根(该属性使用@dynamic 声明)。 OCMock 无法存根该 AFAIK,因此只需直接在托管对象中设置属性 - 在这种情况下不需要模拟。 canLiveStream 不是生成的,也不对应于属性。没有属性 canLiveStream。另请注意,该错误与模拟方法无关,而是与被调用方法中的属性有关。由于 sut 对象已经在运行时生成然后被模拟,所以模拟应该可以访问所有动态属性。如果您认为这不可能,您能否为工作测试提供代码建议,以便我查看您的解决方案是否有效?【参考方案2】:您收到此错误是因为您正在调用 mockSUT 上的方法。由于 CoreData 生成属性访问器的方式,OCMock 无法将其实现复制到部分模拟,因此 ObjC 运行时无法找到它们。
当 checkStreamingPlayabilityForUser 最终调用 self.streamServer 时,self 实际上是 mockSUT,找不到方法 'streamServer'。
如果您只是按照此特定测试所需的方式配置托管对象,这将正常工作。
FWIW 你不应该尝试模拟 NSManagedObject 的实例,执行这些类型的测试的首选方法是在单元测试中简单地创建适合你需要的配置的测试对象。
使用 Core Data 进行数据驱动测试:
如果您创建一个可以为您管理 CoreData 位的 SenTestCase 或 XCTestCase 的子类,将会有所帮助。这个测试用例子类应该提供一个 NSManagedObjectContext 的实例供您的测试使用。
实际测试可能如下所示:
@implementation PeopleViewControllerTest
- (void)testSomething
NSMutableArray *people = [NSMutableArray new];
[self.managedObjectContext performBlockAndWait:^
for (int i=0; i < 100; i++)
Person *p = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
inManagedObjectContext:self.managedObjectContext];
p.firstName = [NSString stringWithFormat:@"First%d", i];
[people addObject:p];
];
//return the people
PeopleViewController *pvc = ...;
id mockPVC = [OCMockObject partialMockForObject:pvc];
[[[mockPVC stub] andReturn:people] fetchedPeople];
//make sure the view controller behaves properly with these 100 people
@end
因此,我们只需创建 100 个实际对象,而不是创建 100 个 NSManagedObject 的模拟实例。
我们不是要测试 CoreData,只是我们建立在一些 NSManagedObjects 之上的逻辑。因此,创建 NSManagedObject 的具体实例是可以的,但应该配置它们以执行您的应用程序的逻辑。
即如果您想检查电子邮件地址验证,您可能有:
p.emailAddress = @"notvalid";
//稍后使用一些模拟对象 [[[partialMock expect] andReturn:p] 人];
【讨论】:
我不同意不模拟 NSManagedObjects。在数据驱动的应用程序中,这些代表了大量可能的逻辑错误。您应该尽可能接近真实情况来测试您的应用程序。您是否有示例说明如何为操作和使用核心数据对象的控制器编写单元测试?我认为 OCMock 应该实现动态属性访问器生成。没那么难。 仅填充 CoreData 对象以适应您的测试有什么问题?没那么难…… 不确定我是否理解。执行直接 alloc init 不会创建核心数据访问器,因此您无法设置任何属性。你有我能看到的更好方法的例子吗,因为我一直在寻找更好的 TDD 方法。 当然,我会修改我的答案以包含一个示例。 FWIW,您也不应该分配初始化托管对象,它确实需要链接到托管对象上下文。以上是关于用于模拟对象的 OCMock 无法识别的选择器的主要内容,如果未能解决你的问题,请参考以下文章