views:

616

answers:

3

So...I'm trying to get unit tests set up in my iPhone App but I'm having some issues. I'm trying to test my model classes but they inherit directly from NSManagedObject. I'm sure this is a problem but I don't know how to get around it.

Everything is building and running as expected but I get this error when calling any method on the class I'm testing:

Unknown.m:0:0 unrecognized selector sent to instance 0xc2b120

If I follow this structure to create my object in my tests I end up with another error entirely but it still doesn't help me.

If I instantiate my model like this:

entry = [[TimeEntry alloc]
        initWithEntity:nil
        insertIntoManagedObjectContext:nil];

Then I end up with this error at runtime:

An NSManagedObject of class 'TimeEntry' must have a valid NSEntityDescription.

If I try it like this:

entry = [[TimeEntry alloc] init];

Then I end up with this error:

unrecognized selector sent to instance 0xc2b120

And if I follow the pattern laid out here:

model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
NSLog(@"model: %@", model);
coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model];
store = [coord addPersistentStoreWithType: NSInMemoryStoreType
                            configuration: nil
                                      URL: nil
                                  options: nil 
                                    error: NULL];
ctx = [[NSManagedObjectContext alloc] init];
[ctx setPersistentStoreCoordinator: coord];

entry = (TimeEntry *)[NSEntityDescription insertNewObjectForEntityForName:@"TimeEntry" inManagedObjectContext:ctx];

Then I get this error:

could not locate an entity named 'TimeEntry' in this model.

Basically my question is this: how can I test a class that inherits from NSManagedObject?

+3  A: 

In order to instantiate an NSManagedObject, you need an entity. Thus what you first tried — either passing nil for the entity or using bare -init (which isn't supported on NSManagedObject) — didn't work. You're doing the right thing by using -[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] to create the object, you just need to:

  1. Make sure that the entity TimeEntry exists in your data model.
  2. Make sure that the entity TimeEntry is associated with the class TimeEntry in your data model.
  3. Make sure that your data model is actually loaded by your tests.

Note that unless you specifically want to test save/delete validation, you generally won't need to add a persistent store to your coordinator. (And if you're using an SQLite persistent store in your app, I'd strongly suggest using one in your tests too; the different persistent store types have different performance characteristics and supported queries.)

For ensuring your data model is loaded, you'll find it much more fruitful I think to actually specify the URL to load it from, instead of just hoping that you've put it in the right place and that -mergedModelFromBundles: will do the right thing. I'd make it a member of your unit test bundle target, so it's compiled into your unit test bundle's Resources. That way you can just use an appropriate NSBundle method to get the path or URL to it.

Finally, you're going to want to put the set-up of your Core Data persistence stack — the model, persistent store coordinator, and a scratch context — in a -setUp method in your test case. Or in a -setUp method of a test case base class, if you want to create more than one test case class. (The same goes for tear-down of the persistence stack and -tearDown methods, of course.)

Chris Hanson
A: 

I created a Sample for a Core Data Test environment on github http://github.com/mbrugger/CoreDataDependentProperties/blob/master/LPAutomatedObserving/Tests/ManagedObjectSenTestCase.m

Inherit your testcases from ManagedObjectSenTestCase.m/h and adjust the following two lines with your test target bundle identifier and data model name

        NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel" ofType:@"mom"];

Code samples:

-(void) setUp
{
    pool = [[NSAutoreleasePool alloc] init];

    NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease];
    [allBundles addObjectsFromArray:[NSBundle allBundles]];

    NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel"
                                                                                            ofType:@"mom"];

    NSURL* modelURL = [NSURL URLWithString:path];
    self.model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];

    self.coordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model] autorelease];


    LPManagedObjectContext* tempContext = [[[NSManagedObjectContext alloc] init] autorelease];


    [tempContext setPersistentStoreCoordinator:coordinator];
    [tempContext setRetainsRegisteredObjects:YES];

    self.context = tempContext;
}

    -(void) tearDown
{
    NSLog(@"BEGIN: ManagedObjectSenTestCase tearDown");
    @try
    {
        self.context= nil;
        self.model = nil;
        self.coordinator = nil;
        [pool release];
        pool = nil;
    }
    @catch (NSException * e)
    {
        NSLog(@"%@",e);
        NSLog(@"%@", [e callStackSymbols]);
        NSLog(@"context reset failed!");
        @throw(e);

    }
    NSLog(@"END: ManagedObjectSenTestCase tearDown");
}

This sample creates the core data stack and you can insert entities into the created context for testing.

Martin Brugger
A: 

I had the same problem. I eventually figured that it was unable to retrieve my model, but being a newbie to iPhone development I couldn't work out how to load it from a URL as per Chris' suggestion.

Loading it from the bundle that the tests were running from is what worked for me:

@implementation WhenWorkingWithATiming

Timing *timing;

NSManagedObjectModel *model;
NSPersistentStoreCoordinator *coordinator;
NSManagedObjectContext *context;


- (void) setUp {
    NSArray *bundles = [NSArray arrayWithObject:[NSBundle bundleForClass:[self class]]];
    model = [[NSManagedObjectModel mergedModelFromBundles:bundles] retain];
    NSLog(@"Model: %@", model);

    coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];

    timing = (Timing *)[NSEntityDescription insertNewObjectForEntityForName:@"Timing" inManagedObjectContext:context];
}

- (void) tearDown {
    [context rollback];
    [context release];
    [coordinator release];
    [model release];
}

- (void) testThatTimingIsInitialised {
    STAssertNotNil(timing, @"should have a timing");
}

@end
Jonathan Moffatt