OCMock with Core Data dynamic properties problem
Asked Answered
S

3

19

I'm using OCMock to mock some Core Data objects. Previously, I had the properties implemented with Objective-C 1.0 style explicit accessors:

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

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

Now I've moved the code to Objective-C 2.0 and want to take advantage of the new @property syntax, and the dynamically-generated method implementations for Core Data objects:

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

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

However, now when I create a mock item, it doesn't seem to handle the dynamic properties:

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

The error looks like this:

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!

Am doing something wrong? Is there another way to mock a Core Data / object with @dynamic prooperties?

Squawk answered 9/12, 2009 at 20:15 Comment(0)
T
21

Also responded to your cross-post on the OCMock Forum

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

Basically he suggests abstracting out your Core Data object's interface to a protocol, and using that protocol instead of the class where you pass instances of your core data object around.

I do this for my core data objects. Then you can use mockForProtocol:

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

Works great! He also suggests creating a non-core data mock implementation of the interface which just synthesizes the properties:

@implementation MockMyItem
@synthesize PDFName;
@end

...

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

I've used this as well, but I'm not sure it adds anything over the mockForProtocol:/stub: approach, and it's one more thing to maintain.

Trackless answered 7/5, 2010 at 18:30 Comment(3)
Just for the sake of completeness could you please, re-post your answer here please. This is not the answer. What if that link dies?Taradiddle
I don't get it, why not just use a real NSManagedObject in your test? Do you have other tests that validate the protocol matches your managed object model?Gillum
I don't do this anymore. It's much simpler to just create an in-memory store and use real CD objects. At the time this question was posted, using Core Data in unit tests was very clunky, but Apple has improved its unit testing support significantly in the past few years.Trackless
F
11

The above answer didn't satisfy me, because I didn't like to create a protocol for that. So I found out that there is an easier way to do that. Instead of

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

Just write

[[[mockItem stub] andReturn:@"fakepath.pdf"] valueForKey:@"PDFName"];
Fisch answered 7/11, 2013 at 8:50 Comment(0)
W
3

One of solutions is using a protocol, which is intended to substitute it's original interface, but it could be a bit heavy and leads to significant amount of code you should duplicate.

Personally, I found a way to make it lightweight:

Create a simple category, for instance, inside your unit testing file, just before your unit testing class:

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

Also, you can keep it in separate file, but make sure, that this file is not a part of your production target. That is why I prefer to keep it in the same test file, where I want to use it.

The huge advantage of this method, is that you should not copy methods, that are created by XCode to support relationships. Also you can put to this category only methods you are going to call inside your tests.

There are some caveats, though, for example, you should add another methods inside the category, to support setters, when you are going to check, how correct your code changes the properties of your managed object:

- (void)setPDFName:(NSString *)name{};
Weaponless answered 23/11, 2013 at 20:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.