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

Luther Baker
  • 7,236
  • 13
  • 46
  • 64
  • 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. – d512 Apr 17 '14 at 20:52

5 Answers5

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"];
    ...
}
Till
  • 27,559
  • 13
  • 88
  • 122
  • 1
    @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. – Manav Jan 31 '13 at 08:46
  • Thank you so much. Lost two solid days trying to figure this out! – user798719 Nov 20 '13 at 00:21
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

Luther Baker
  • 7,236
  • 13
  • 46
  • 64
  • This answer is good as well, because it provides all the information you need to understand and fix the issue. – Ben G Jan 24 '13 at 12:11
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];
}
user966697
  • 271
  • 2
  • 8
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;
}
Jano
  • 62,815
  • 21
  • 164
  • 192
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à!

Kevin Delord
  • 2,498
  • 24
  • 22