In my article iOS unit testing tips & tricks, I explained how I use an in-memory managed object context in my unit tests. This article updates some code from that section. The old code did not work in a project that used versioned models.
Specifying the model name
The old version of the script had the following in it:
[NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]]
This is problematic. If your project has a model with multiple versions, you will get an error saying Can't merge models with two different entities named 'MyEntityName'. This is because it finds all the versions of your model in [NSBundle allBundles]
, treats them as separate models, and tries to merge them. Merging two almost identical models will throw errors.
You can solve the problem by altering the code to only get the model you want to use:
// Old
model = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
// New
static NSString *testTargetName = @"Tests";
NSBundle *testsBundle;
for (NSBundle *bundle in [NSBundle allBundles]) {
// @"Tests" is the name of the test target.
if ([[b objectForInfoDictionaryKey:@"CFBundleExecutable"] isEqualToString:testTargetName]) {
testsBundle = bundle;
break;
}
}
NSURL *modelURL = [NSURL fileURLWithPath:[testsBundle pathForResource:@"YourModelName" ofType:@"momd"]];
self.model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];
The most significant change is that we now specify the model name. We could try guessing the model name, or get the first model we find and use it. But if we guess wrong, or you're in a project with multiple modules, we're screwed. So we might as well specify it. Replace @"YourModelName"
with the name of the .xcdatamodel
or .xcdatamodeld
file. Off topic, but FYI: In Xcode's templates, the models is named after the project itself. It's in the Resources group.
The full code
// Header
@interface ManagedObjectContextTestHelper : NSObject {
}
-(id)initWithModelName:(NSString *)name;
@property (nonatomic, retain) NSManagedObjectModel *model;
@property (nonatomic, retain) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, retain) NSPersistentStore *store;
@property (nonatomic, retain) NSManagedObjectContext *context;
@end
// Implementation
@implementation ManagedObjectContextTestHelper
@synthesize model, coordinator, store, context;
-(id)initWithModelName:(NSString *)name {
static NSString *testTargetName = @"Tests";
if (self = [super init]) {
NSBundle *testsBundle;
for (NSBundle *bundle in [NSBundle allBundles]) {
if ([[bundle objectForInfoDictionaryKey:@"CFBundleExecutable"] isEqualToString:testTargetName]) {
testsBundle = bundle;
break;
}
}
NSURL *modelURL = [NSURL fileURLWithPath:[testsBundle pathForResource:name ofType:@"momd"]];
self.model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];
self.coordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model] autorelease];
self.store = [coordinator addPersistentStoreWithType: NSInMemoryStoreType
configuration: nil
URL: nil
options: nil
error: NULL];
self.context = [[[NSManagedObjectContext alloc] init] autorelease];
self.context.persistentStoreCoordinator = coordinator;
}
return self;
}
-(void)dealloc {
[context release];
[store release];
[coordinator release];
[model release];
[super dealloc];
}
@end