Upward Mobility: Unit Testing Core Data

There's no reason to leave your database-centric code untested

One of the more common issues that arises in creating OCUnit tests in iOS is how to test code that uses Core Data. There are several challenges, but with a little foresight, you can be sailing right along.

The first issue that most people encounter is getting access to the managed object context. If, like most people, you’ve followed the code snippets that Apple provides, or followed a tutorial on the web, you hung it off the AppDelegate, and use

[[UIApplication sharedApplication] delegate]

to get a handle on the context when you need it. Alternatively, some people pass the context all the way down the view container chain, making it a property on each controller. I find this incredibly painful, and since most applications only have a single database instance open at once, there’s no reason not to keep the context around globally.

In any event, the problem with having your Core Data specific classes get the context from the sharedApplication is that when you run OCUnit tests, sharedApplication returns nil, and there’s no way to mock it. But, this is easily solved by creating a helper class that you use to access the context.

CoreDataHelper.h
NSManagedObjectContext *testingContext;

@interface CoreDataHelper
+(NSManagedObjectContext *) getManagedObjectContext;
@end

CoreDataHelper.m
#import "CoreDataHelper.h"
#import "MyApplicationAppDelegate.h"

@implementation CoreDataHelper

+(NSManagedObjectContext *) getManagedObjectContext {
   if ([UIApplication sharedApplication] == nil) {
      return testingContext;
   }
   MyApplicationAppDelegate *delegate = 
       (MyApplicationAppDelegate *)
          [[UIApplication sharedApplication] delegate];
   return [delegate managedObjectContext];
}
@end

Now, whenever you need to get the context to make a Core Data call, you can just call

[CoreDataHelper getManagedObjectContext]

In your unit tests, you can have your setUp method instantiate the context and set the global variable testingContext to it. This brings us to the other challenge, creating the context. There are a few things you need to do in order to make it work. First, you need to make sure that your data model file is included in your OCUnit project. Second, you need to include the Core Data framework in the project. The last step is to create an empty, in-memory version of the database in your setUp method, which should look something like this:

NSBundle *bundle = 
    [NSBundle bundleForClass:NSClassFromString(@"SomeClass")];
NSString* path = 
   [bundle pathForResource:@"ModelName" ofType:@"momd"];
NSURL *modURL = [NSURL URLWithString:path];
NSManagedObjectModel *model = 
   [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];  
NSPersistentStoreCoordinator *coord = 
   [[NSPersistentStoreCoordinator alloc] 
        initWithManagedObjectModel: model];
NSPersistentStore *store = 
       [coord addPersistentStoreWithType:NSInMemoryStoreType 
            configuration:nil URL:nil options:nil error:nil];
testingContext = [[NSManagedObjectContext alloc] init];
[testingContext setPersistentStoreCoordinator: coord];

In the above example, SomeClass should be a class that’s in your OCUnit project, and ModelName should be the filename of your Core Data object model. To be thorough, you might want to keep a handle to the store and coordinator, and call removePersistentStore in the tearDown method.

Now, you can write unit tests against your Core Data dependent classes, and they will seamlessly use either the live version of the database in the appDelegate, or your testing one, depending on whether the app is running live or you’re running unit tests.

 

tags: ,