Core Data and OCMock
Models backed by a data persistence layer are too important to simply punt on proper testing. The rest of the app expects the data models to work as designed; data persistence and all. This article presents a solution to unit testing Core Data models using OCMock and Magical Record.
The straight mocking problem
If you’re familiar with OCMock, this code may look familiar. If you’ve never used OCmock before, take a look at this tutorial, and the OCMock site for additional resources. Anyway, let’s pretend we have a simple NSManaged object we’re innocently trying to test:
The code above creates a new Person object and inserts it into the default NSManagedObjectContext using helpers provided by Magical Record. A partial mock of ‘Person’ object is created with OCMock. However, whenever a property of the NSManagedObject is accessed, we receive an odd ‘unknown selector’ error.
Core Data implements property accesors through @dynamic properties. Any property marked @dynamic means the class will figure out howto respond to the selector at runtime. In the case of Core Data, the NSManagedObject provides accessors for us to read these fields from the persistent store. Unlike @property, an automatic ivar is not created when using @dynamic. When an NSManagedObject is passed to OCMock mock object or partialMockForObject the @dynamic selectors aren’t found and results in an error.
Never, ever mock Core Data
Don’t directly mock Core Data. There is too much proprietary weird stuff going on to trust the test completely. If this feels dirty to you, another option is to abstract any business operations out of your NSManagedObjects into a service layer which can be properly mocked.
In memory store
When testing with XCTest, the
+ setUp and
+ tearDown methods create a new in-memory Core Data stack and tear it down once the tests have completed. I find this provides the speed and test isolation necessary to ‘trust’ the tests between classes.
Stub without wrecking @dynamic properties
In this example we don’t be using a Core Data mock directly, but will still make use of OCMock stubs.
Programmer discovers howto use OCMock with Core Data using this one weird trick!
Creating two NSManagedObjects, one for use within assertions and another to feed into OCMock for stubbing, etc works because of this reason. (taken from OCMock docs)
Creates a mock object that can be used in the same way as anObject. When a method that is not stubbed is invoked it will be forwarded to anObject. When a stubbed method is invoked using a reference to anObject, rather than the mock, it will still be handled by the mock.
In laymens terms, this means by passing
modelForMocking to partialForMock:, the other Person model,
realPersonModel is also affected. Except now that
realPersonModel can finally resolve it’s @dynamic selectors, we can contiue to test in comfort.