OCMock与Core Data动态属性问题

时间:2009-12-09 20:15:17

标签: cocoa unit-testing core-data ocmock

我正在使用OCMock来模拟一些Core Data对象。以前,我使用Objective-C 1.0样式显式访问器实现了属性:

// -- Old Core Data object header
@interface MyItem : NSManagedObject {}
- (NSString *) PDFName;
- (void) setPDFName:(NSString *)pdfName;
@end

// -- implementation provides generated implementations for both getter and setter

现在我已将代码移至Objective-C 2.0,并希望利用新的@property语法,以及为Core Data对象动态生成的方法实现:

// -- New Core Data object header
@interface MyItem : NSManagedObject {}
@property (nonatomic, retain) NSString *PDFName;
@end

// -- Core Data object implementation
@implementation MyItem
@dynamic PDFName;
@end

但是,现在当我创建一个模拟项时,它似乎无法处理动态属性:

// -- creating the mock item
id mockItem = [OCMockObject mockForClass:[MyItem class]];
[[[mockItem stub] andReturn:@"fakepath.pdf"] PDFName]; // <-- throws exception here

错误如下所示:

Test Case '-[MyItem_Test testMyItem]' started.
2009-12-09 11:47:39.044 MyApp[82120:903] NSExceptionHandler has recorded the following exception:
NSInvalidArgumentException -- *** -[NSProxy doesNotRecognizeSelector:PDFName] called!
Stack trace: 0x916a4d24 0x92115509 0x97879138 0x978790aa 0x9090cb09 0x97820db6 0x97820982 0x10d97ff 0x10d9834 0x9782005d 0x9781ffc8 0x20103d66 0x20103e8c 0x20103642 0x20107024 0x20103642 0x20107024 0x20103642 0x20105bfe 0x907fead9 0x977e4edb 0x977e2864 0x977e2691 0x90877ad9 0xbf565 0xbf154 0x107715 0x1076c3 0x1082e4 0x89d9b 0x8a1e5 0x894eb 0x907e81c7 0x978019a9 0x978013da 0x907dd094 0x907ea471 0x9478c7bd 0x9478c1b9 0x94784535 0x5ede 0x326a 0x5
Unknown.m:0: error: -[MyItem_Test testMyItem] : *** -[NSProxy doesNotRecognizeSelector:PDFName] called!

我做错了什么?是否有另一种方法可以使用@dynamic prooperties模拟核心数据/对象?

3 个答案:

答案 0 :(得分:20)

还回复了OCMock Forum

上的交叉帖子

查看http://iamleeg.blogspot.com/2009/09/unit-testing-core-data-driven-apps.html

基本上,他建议将Core Data对象的接口抽象为协议,并使用该协议而不是传递核心数据对象实例的类。

我为核心数据对象执行此操作。然后你可以使用mockForProtocol:

id mockItem = [OCMockObject mockForProtocol:@protocol(MyItemInterface)];
[[[mockItem expect] andReturn:@"fakepath.pdf"] PDFName];

效果很好!他还建议创建一个非核心数据模拟实现的接口,它只是合成属性:

@implementation MockMyItem
@synthesize PDFName;
@end

...

id <MyItemInterface> myItemStub = [[MockMyItem alloc] init] autorelease];
[myItem setPDFName:@"fakepath.pdf"];

我也使用过这个,但是我不确定它是否会在mockForProtocol:/ stub:方法上添加任何内容,而且还需要维护它。

答案 1 :(得分:11)

上述答案并不能让我满意,因为我不喜欢为此制定协议。所以我发现有一种更简单的方法可以做到这一点。 而不是

[[[mockItem stub] andReturn:@"fakepath.pdf"] PDFName]; // <-- throws exception here

只需写下

[[[mockItem stub] andReturn:@"fakepath.pdf"] valueForKey:@"PDFName"];

答案 2 :(得分:3)

其中一个解决方案是使用一个协议,该协议旨在替代它的原始接口,但它可能有点沉重,导致您应该复制大量代码。

就个人而言,我找到了一种让它轻量化的方法:

在单元测试文件中创建一个简单的类别,就在单元测试类之前:

@implementation MyItem(UnitTesing)
- (NSString *)PDFName{return nil;};
@end

此外,您可以将其保存在单独的文件中,但请确保此文件不是生产目标的一部分。这就是为什么我更喜欢将它保存在我想要使用它的同一个测试文件中。

此方法的巨大优势在于,您不应该复制由XCode创建的方法来支持关系。此外,您只能在测试中调用您要调用的方法。

但是,有一些注意事项,例如,您应该在类别中添加其他方法,以支持setter,当您要检查时,代码更改托管对象属性的更正方式:

- (void)setPDFName:(NSString *)name{};