Unit Test can't find Core Data model file - iphone

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

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

Related

How to write to AppName-Info.plist

i added a key called "App" to my AppName-Info.plist manually , i can get the value from it by calling this code
NSBundle *mainBundle;
mainBundle = [NSBundle mainBundle];
NSString *value = [mainBundle objectForInfoDictionaryKey:#"App"];
NSLog(#"App: %#",value);
But what i couldn't do is changing the value with any code.. is it possible ? and if yes how can it be done ?
thanks for help in advance :)
You should consider NSUserDefaults or if you want to modify a bundled .plist try this.
NSString* plistFilePath = nil;
NSFileManager* manager = [NSFileManager defaultManager];
if ((plistFilePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"mySpecial/PathTo.plist"]))
{
if ([manager isWritableFileAtPath:plistFilePath])
{
NSMutableDictionary* infoDictioio = [NSMutableDictionary dictionaryWithContentsOfFile:plistFilePath];
[infoDictio setObject:#"foo object" forKey:#"fookey"];
[infoDictio writeToFile:plistFilePath atomically:NO];
[manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:[[NSBundle mainBundle] bundlePath] error:nil];
}
}
You should not modify your apps Info.plist file (or anything in your app's bundle) at runtime. This is bad practice and will also break your bundles code signature (which will result in not being able to launch the app anymore!).
If you want to store settings you should have a look at NSUserDefaults.
It's not possible.
By default the AppName-Info.plist isn't copied into the bundle in the Copy Bundle Resources phase of the build.
So if you want to have a plist which you can write to an option would be to create it at run time in the temporary files location and read/write to it there.
This is a great place to research how to do it.

problems creating static library with CoreData - Cannot create an NSPersistentStoreCoordinator with a nil model

How do I reference the Model that I have created in my static library project?
This returns null and throws and error because the resources live in this static library:
//this code is in the static library
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil) {
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"eCommerceEngine" withExtension:#"mom"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
How do I change this to pull from this static library?
On the iPhone, static libraries have a .a extention and can only contain code. This means that any resources (xibs, images, etc…) must be packed either in a bundle or shipped separately than the library.
See iOS Library With Resources
If you want to use a preconfigured model from a library, I would suggest building the model programmatically, using the Entity API, and not use a model file at all.

Runtime change the language/localization in Three20

Is it possible to change the Three20 language/localization at runtime without restarting the app?
Currently, I managed to change the language via altering the value of AppleLanguages in the main.m
There's a "hack" for it. You can load your own NSBundle with the localized text and use that NSBundle instead. Note that if the localized language file is missing, the app won't run, so make sure you set a correct language.
Above your AppDelegate implementation, add a custom NSBundle declaration:
static NSBundle *bundle = nil;
And then load the language you desire into that bundle:
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:#"he", nil] forKey:#"AppleLanguages"];
NSLocale* locale = TTCurrentLocale();
NSString *path = [[NSBundle mainBundle] pathForResource:[locale localeIdentifier] ofType:#"lproj" ];
bundle = [[NSBundle bundleWithPath:path] retain];
You will add a custom function in your AppDelegate to get the localized text too (instead of NSLocalizedString)
///////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSString*)get:(NSString*)key {
return [bundle localizedStringForKey:key value:nil table:nil];
}
To make things easier, you can add a static function in the pch file:
#import "AppDelegate.h"
#define MyLocalizedString(key, alt) [AppDelegate get:key]

a dynamic way of doing a questionnaire in iphone

i managed to read my question from my question.plist for question and answer.
self.view.backgroundColor = [UIColor colorWithRed:99.0/255.0 green:162.0/255.0 blue:223.0/255.0 alpha:1.0];
self.title = #"Game"; //Set title.
// Path to the plist (in the application bundle)
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Question" ofType:#"plist"];
NSArray *array = [NSArray arrayWithContentsOfFile:path];
NSDictionary *dict = [array objectAtIndex:0];
questionlabel.text = [dict valueForKey:#"Question"];
a button here to call for QR code scanner, after scanning it will check if the answer is correct or wrong and direct the user to another question in the question.plist(objectAtIndex:1) if is correct if not it will pop up and show is incorrect
i can do it in a static way of creating a few more to xib to create other question statically, however i know this is a troublesome way to do it. anybody know how can i do it in a dynamic way?
thanks in advances
Desmond
What does the QR code scanner have to do with anything? I think it is not strictly relevant to your question, so I'll only cover exactly what you're asking. QR code scanning is not a part of the iOS and is provided by external libraries.
Add an integer i-var to the view controller:
#interface WhateverYouNamedYourViewController : UIViewController {
// ....
int currentQuestion;
// ....
}
// ....
#end
Then, when loading the question and updating the UI, use that i-var to construct the NSString from which you'll load the question.
NSString *questionFileName = [NSString stringWithFormat:
#"Question%d", currentQuestion];
NSString *path = [[NSBundle mainBundle] pathForResource:questionFileName
ofType:#"plist"];
Alternatively, consider storing all questions in a single plist whose root is an array. Then use NSArray's -objectAtIndex: method to grab the current question (simply pass currentQuestion as the first argument).
In any case, the place where you check for answer correctness (in a quiz) or where you record the answer (in a questionnaire) will need to update the current question and update UI:
currentQuestion++;
[self updateUI];
Updating UI reads the next question and updates labels and buttons appropriately.

Error Building Core Data Stack in Unit Tests

I am trying to get started with unit testing an app that uses Core Data. In the setUp method of my unit first test, I can get the path to my data model but for some reason cannot convert it to a NSURL.
My setUp method is:
- (void)setUp {
NSBundle *bundle = [NSBundle bundleWithIdentifier:#"com.testcompany.LogicTests"];
STAssertNotNil(bundle, #"Error finding bundle to create Core Data stack.");
NSString *path = [bundle pathForResource:#"DataModel" ofType:#"momd"];
STAssertNotNil(path, #"The path to the resource cannot be nil.");
NSURL *modelURL = [NSURL URLWithString:path];
STAssertNotNil(modelURL, #"The URL to the resource cannot be nil. (tried to use path:%#, modelURL is %#)", path, modelURL);
...
}
The error I'm getting is:
/Users/neall/iPhone Apps/TestApp/UnitLogicTests.m:24:0 "((modelURL) != nil)" should be true. The URL to the resource cannot be nil. (tried to use path:/Users/neall/iPhone Apps/TestApp/build/Debug-iphonesimulator/LogicTests.octest/DataModel.momd, modelURL is (null))
I've checked the filesystem and the directory /Users/neall/iPhone Apps/TestApp/build/Debug-iphonesimulator/LogicTests.octest/DataModel.momd exists.
What am I missing here?
Thanks!
Try using [NSURL fileURLWithPath:path] instead to construct the url
Double check that you are seeing a directory called DataModel.momd at /Users/neall/iPhone Apps/TestApp/build/Debug-iphonesimulator/LogicTests.octest/DataModel.momd.
If you added a xcdatamodel file by the Add New File... command in Xcode, you would only have one file and it would be DataModel.mom (no trailing d). If that's the case, changing the
NSString *path = [bundle pathForResource:#"DataModel" ofType:#"momd"];
to
NSString *path = [bundle pathForResource:#"DataModel" ofType:#"mom"];
will fix your immediate issue.
You want to use the fileURLWithPath: that Claus suggested as well.
If you want to do versioning of your model in the future and you currently have only a .mom file, select your DataModel.xcdatamodel file in XCode and go to Design -> Data Model -> Add Model Version. This will force the creation of the DataModel.momd directory with the DataModel.mom file in it. You can just delete the new version it adds into that directory and your original tests will work.
xcdatamodel should also be added to
Project -> Targets -> "unit test target" -> build phases -> compile sources
After spending several hours stacking in July 2014 this post was one of several that in part led me to the working solution.
We somehow managed to break the surprisingly fragile (and mysterious) mechanism that links the bundle that your source code lives in to the bundle that runs the unit test. Further you might have a misnamed xcdatamodel. See comments for explanations:
-(NSManagedObjectContext *) getManagedObjectContext
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
//Replace MyClass with class that is from your data model
//really any of your classes should work
NSBundle * bundle = [NSBundle bundleForClass:[MyClass class]];
//You can uses this line to figure you what your bundle is actually named
//In my case the because my PRODUCT_NAME had spaces in it they was replaced with '-'
//(dashes) and I couldn't divine it from the info.plist and the Build Settings.
NSString * ident =[bundle bundleIdentifier];
//This will show you where your app is actually out building temporary files
//The exact location appears to change every version or to of Xcode so
//this is useful for figuring out what your model is named
NSString * bundlePath =[bundle bundlePath];
//Here replace Name_of_model_without_the_dot_xcdatamodel with the name of your
//xcdatamodel file without an extension
//Some tutorials will have you use AppName.xcdatamodel others will simply name it
//DataModel.xcdatamodel.
//In any event if bothe path and path1 return null then check the
//bundlePath by going to Finder and pressing Command-Shift-G and pasting
//bundlePath into the pop-up. Look around for a mom or momd file thats the name you want!
NSString* path = [bundle
pathForResource:#"Name_of_model_without_the_dot_xcdatamodel"
ofType:#"momd"];
//If the above 'path' and 'path1' is not then you want to use this line instead
NSString* path1 = [bundle
pathForResource:#"Name_of_model_without the_dot_xcdatamodel"
ofType:#"mom"];
//the above path lines are simply so you can trace if you have a mom or a momd file
//replace here appropriately
NSURL *modelURL = [bundle URLForResource:#"Name_of_model_without the_dot_xcdatamodel"
withExtension:#"momd"];
//the rest is boiler plate:
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *psc =
[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
[psc addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil URL:nil options:nil error:nil];
[moc setPersistentStoreCoordinator:psc];
return moc;
}
Here is how you might use the above context:
-(void)testMyStuff
{
NSManagedObjectContext* context=[self getManagedObjectContext];
MyClass *myobj=[NSEntityDescription insertNewObjectForEntityForName:#"MyClass"
inManagedObjectContext:context];
}
One final note you may also have to add your source files and xcmodel under the "Compile Sources" of build phases. This unfortunately changes with almost every version of Xcode. For Xcode 5: