Unit Test can't find Core Data model file
Asked Answered
C

5

17

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

Cinelli answered 14/4, 2011 at 12:5 Comment(1)
Thank you for mentioning that you have to add your data model to the test project's Compiled Sources listing. That's what I was missing.Leitao
N
35

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"];
    ...
}
Novation answered 18/4, 2012 at 11:31 Comment(2)
@LutherBaker While this seems to be the best workaround among all solutions for now, I do not think this is the right answer. Unit testing should not require one to modify the application source. So while here you're accessing the bundle in the test case, doing a bundleForClass: seems okay, but if you have some application code that does a [NSBundle mainBundle] (a common enough idiom, and not deprecated AFAIK), that class cannot be unit tested.Mullock
Thank you so much. Lost two solid days trying to figure this out!Sackbut
C
6

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

Cinelli answered 14/4, 2011 at 19:40 Comment(1)
This answer is good as well, because it provides all the information you need to understand and fix the issue.Unpolled
C
3

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];
}
Carousel answered 26/2, 2014 at 12:25 Comment(0)
B
2

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;
}
Boise answered 3/10, 2011 at 16:26 Comment(0)
S
0

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à!

Strophic answered 3/1, 2018 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.