I concur with @Abizern: pass an instance to a NSManagedObjectContext
around in your code instead of relying on your app delegate, a global variable, or a custom helper singleton.
Dependency Injection
If you know that some controller will need access, add a NSManagedObjectContext
parameter to its init method and keep a strong reference to it:
@interface SomeController : NSObject
@property (nonatomic, strong, readwrite) NSManagedObjectContext *context;
- (instancetype)initWithContext:(NSManagedObjectContext *)context;
@end
This is the minimum requirement to do "dependency injection". You don't need a fancy framework to do the injection for you. Instead, in your app you assign your usual NSManagedObjectContext
instance which probably uses a SQLite store. In your tests, you create a separate NSManagedObjectContext
with an in-memory store and pass it to SomeController
. This would even work with (partial) mocks using OCMock.
There are some good examples at objc.io #4, especially Chris Eidhof's example app: http://www.objc.io/issue-4/full-core-data-application.html
How to access a MOC
Looking at the objc.io example code at GitHub, you'll see a PersistentStack
helper class which takes care of initializing the managed object context for your application. Chris uses an abstract test case subclass to provide a testing context.
The overall guideline is this:
- Don't rely on a singleton helper class throughout the code because it's hard to replace its context with a test context. That's a peculiarity I have discovered and which is due to XCTest being "injected" into the running app code. There were working examples on the web, but they didn't do what I expected with Xcode 5.1 and XCTest.
- Instead, prepare a
NSManagedObjectContext
once where appropriate. Pass managed objects around and use the managed objects to get access to the context.
Florian Kugler states it this way:
Managed objects are supposed to be passed around in the application, crossing at least the model-controller barrier, and potentially even the controller-view barrier. The latter is somewhat more controversial though, and can be abstracted in a better way by e.g. defining a protocol to which an object must conform in order to be consumed by a certain view, or by implementing configuration methods in a view category that bridge the gap from the model object to the specifics of the view.
Anyway, we shouldn’t limit managed objects to the model layer and pull out their data into different structures as soon as we want to pass them around. Managed objects are first-class citizens in a Core Data app and we should use them accordingly. For example, managed objects should be passed between view controllers to provide them with the data they need.
In order to access the managed object context we often see code like this in view controllers:
NSManagedObjectContext *context =
[(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
If you already pass a model object to the view controller, it’s much better to access the context directly via this object:
NSManagedObjectContext *context = self.myObject.managedObjectContext;
This removes the hidden dependency on the application delegate, and makes it much more readable and also easier to test.
This is the best overall advice I got. Now I'm able to test Core Data usage where I need to. Previously, accessing the context via global singleton class (custom class or app delegate) was convenient to use in production, but proves difficult to validate in tests.