How to unit test logic on NSManagedObjects
I recently started using OCUnit to test the logic in my code. One issue I ran into was how to test logic performed on Core Data objects - subclasses of NSManagedObject.
I naively thought that I could just alloc and init an NSManagedObject, set some of its properties, and test my method on that object. However, this doesn’t work. If you notice, NSManagedObject uses the @dynamic directive instead of @synthesize. This means that NSManagedObjects do not initialize getters and setters for their properties. With @dynamic, the property’s getter and setters are handled elsewhere.
NSManagedObjects use @dynamic for its properties so that it can load its attributes lazily. This allows NSManagedObjects to read off the disk if its attributes that have not been faulted in. This is the reason why NSManagedObjects don’t create instance variables for its properties. If it did, every time you fetched an NSManagedObject, it would need to bring in all of its connected objects. This is particularly useful when NSManagedObjects have parent child relationships.
You normally create NSManagedObjects with the method, + (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context, but, you don’t actually want to save and delete test NSManagedObjects to your production NSPersistentStore. So when you set up the Core Data stack in your unit tests, set your persistent store type as NSInMemoryStoreType - this means that the store only exists in memory and does not write to disk.
By using NSInMemoryStoreType, you get full simulation of the Core Data environment, without actually saving NSManagedObjects to disk.
I pretty much followed Graham Lee’s blog post and I could run OCUnit on my Core Data objects.
However, there was one gotcha, though. I have a separate OCUnit target, so I first had an issue finding my NSManagedObjectModel. I needed to make sure I was searching for it in the bundle associated with my test classes. So in order to find it, I used this:
NSArray *bundles = [NSArray arrayWithObject:[NSBundle bundleForClass:[self class]]];
model = [NSManagedObjectModel mergedModelFromBundles:bundles];
This explicitly asks to search for models associated with my test class - make sure to add your model - your .xcdatamodeld file into your test target.
Once I was searching for the model in the correct place, I was able to run my tests without any build failures.