Unit Test can't find Core Data model file

I've created a project with a Core Data model in it. The application looks for the model file (.momd) and runs just fine.

Unfortunately, the unit test keeps returning null:

NSURL *dataModelURL = [[NSBundle mainBundle] URLForResource:@"myDataModel" withExtension:@"momd"];

I can see the myDataModel.xdatamodeld folder and file in BOTH the main target and the unit testing target's Compile Sources directory - but that doesn't seem to be enough. What else am I missing in the unit test target?

Thanks, -Luther

Answers


Unfortunately, a unit test target does not use the application's main bundle but it creates a special UnitTest-bundle. So if you need to use bundled resources (like a Core Data model) within your tests, you need to work around that issue.

The most simple and most flexible workaround would be using the bundleForClass: method of NSBundle within your testing code. The parameter for that method can simply be given by [self class] within your tests. That way you can reuse this code without having to adjust the bundle identifiers in multiple projects.

Example:

- (void)testBundleLocation
{
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    NSURL *url = [bundle URLForResource:@"myDataModel" withExtension:@"momd"];
    ...
}

The answer has to do with the bundle. A unit test target doesn't use the 'main' bundle. It creates its own bundle which, in my case, defaulted to 'com.yourcompany.UnitTest' - straight out of the [Target]-info.plist.

The corrected solution then looks like this:

NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.UnitTests"];
NSURL *url = [bundle URLForResource:@"myDataModel" withExtension:@"momd"];

Thanks


Had a similar problem, i solved it using the OCMock framework, so i did not need to change the application code

@interface TestCase()
    @property (nonatomic, strong) id bundleMock;
@end

@implementation TestCase

- (void)setUp
{
    self.bundleMock = [OCMockObject mockForClass:[NSBundle class]];
    [[[self.bundleMock stub] andReturn:[NSBundle bundleForClass:[self class]]] mainBundle];
    [super setUp];
}

- (void)tearDown
{
    [self.bundleMock stopMocking];
    [super tearDown];
}

This method will get your bundle from any target. However, for each target you add, you have to manually add the plist bundle identifier to the identifiers array, because there is no way to get it programmatically. The advantage is that you can use the same code for testing or running the application.

+(NSBundle*) getBundle 
{
    NSBundle *bundle = nil;

    // try your manually set bundles
    NSArray *identifiers = [NSArray arrayWithObjects: @"com.your.application",
                                                      @"com.your.test",
                                                      nil];
    for(NSString *bundleId in identifiers) {
        bundle = [NSBundle bundleWithIdentifier:bundleId];
        if (bundle!=nil) break;
    }

    // try the main bundle
    if (bundle==nil) bundle = [NSBundle mainBundle];

    // abort
    assert(bundle!=nil && "Missing bundle. Check the Bundle identifier on 
           the plist of this target vs the identifiers array in this class.");

    return bundle;
}

My problem was indeed the wrong Bundle! As I was trying to use a database from/within a Framework I 'simply' has to load the db from the corresponding Bundle !

Here is some code in Swift4 using MagicalRecord:

// Load the bundle
let frameworkBundle = Bundle(for: AClassFromTheFramework.self)
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [frameworkBundle])
// Use the new `managedObjectModel` by default
MagicalRecord.setShouldAutoCreateManagedObjectModel(false)
NSManagedObjectModel.mr_setDefaultManagedObjectModel(managedObjectModel)
// Load the database 
MagicalRecord.setupCoreDataStack(withAutoMigratingSqliteStoreNamed: "db.sqlite")

And voilà!


Need Your Help

How to use a RELATIVE path with AuthUserFile in htaccess?

.htaccess .htpasswd

I have a .htaccess that uses basic authentication. It seems the path to the .htpasswd file isn't relative to the htaccess file, but instead to the server config.

Go: marshal []byte to JSON, giving a strange string

json go

When I try to marshal []byte to JSON format, I only got a strange string.