NSURLSession cancel Task - cocoa-touch

I create new NSURLSession with following configs
if (!self.session) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:[self uniquieIdentifier]];
config.discretionary = NO;
self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
and on after pressing a button I am trying to stop all current download tasks.
[[[self session] delegateQueue] setSuspended:YES];
[[self session] invalidateAndCancel];
Nevertheless I get responses in delegate method didFinishDownloadingToURL, and I am pretty sure that no new sessions or download task are created after this point. How to stop all task from happening?

I do not reccommend to use invalidateAndCancel method cause the queue and its identifier keeps invalidated and cannot be reused untill you reset the whole device.
NSURLSession class reference
I use this code to cancel all pending tasks.
- (void) cancelDownloadFiles
{
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionTask *_task in downloadTasks)
{
[_task cancel];
id<FFDownloadFileProtocol> file = [self getFileDownloadInfoIndexWithTaskIdentifier:_task.taskIdentifier];
[file.downloadTask cancel];
// Change all related properties.
file.isDownloading = NO;
file.taskIdentifier = -1;
file.downloadProgress = 0.0;
}
}];
cancel = YES;
}

That is the expected behaviour, when you cancel tasks on a session they might still call the delegate method.
Have you check the state of the given task? It should be NSURLSessionTaskStateCanceling.

Related

NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : Download stops after the app is pushed into background

This method sets the background object.
- (void) downloadWithURL: (NSMutableArray *)urlArray
pathArr: (NSMutableArray *)pathArr
mediaInfo: (MediaInfo *)mInfo
{
bgDownloadMediaInfo = mInfo;
reqUrlCount = urlArray.count;
dict = [NSDictionary dictionaryWithObjects:pathArr
forKeys:urlArray];
mutableDictionary = [dict mutableCopy];
backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"XXXXX"];
backgroundConfigurationObject.sessionSendsLaunchEvents = YES;
backgroundConfigurationObject.discretionary = YES;
backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigurationObject
delegate: self delegateQueue: [NSOperationQueue currentQueue]];
self.requestUrl = [urlArray objectAtIndex:0];
download = [backgroundSession downloadTaskWithURL:self.requestUrl];
[download resume];
}
These are the completion handlers.
#pragma Mark - NSURLSessionDownloadDelegate
- (void)URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL: (NSURL *)location
{
LogDebug(#"Download complete for request url (%#)", downloadTask.currentRequest.URL);
NSString *temp = [mutableDictionary objectForKey:downloadTask.currentRequest.URL];
NSString *localPath = [NSString stringWithFormat: #"%#",temp];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationURL = [NSURL fileURLWithPath: localPath];
NSError *error = nil;
[fileManager moveItemAtURL:location toURL:destinationURL error:&error];
LogDebug(#"Moving download file at url : (%#) to : (%#)", downloadTask.currentRequest.URL, destinationURL);
reqUrlCount --;
downloadSegment ++;
// Handover remaining download requests to the OS
if ([finalUrlArr count] != 0) {
// remove the request from the array that got downloaded.
[finalUrlArr removeObjectAtIndex:0];
[finalPathArr removeObjectAtIndex:0];
if ([finalUrlArr count] > 0) {
// proceed with the next request on top.
self.requestUrl = [finalUrlArr objectAtIndex:0];
download = [backgroundSession downloadTaskWithURL:self.requestUrl];
[download resume];
}
}
if ([adsArray count] > 0) {
adsArrayCount --;
// delegate back once all the ADs segments have been downloaded.
if (adsArrayCount == 0) {
for (int i = 0; i < [adsArray count]; i++) {
NSArray *ads = [adsArray objectAtIndex: i];
for (int j = 0; j < [ads count]; j++) {
MediaInfo *ad = [ads objectAtIndex: j];
[self setDownloadComplete: ad];
// skip sending downloadFinish delegate if the media is marked as downloadDone
if (!ad.downloadDone) {
[delegate MediaDownloadDidFinish: ad.mediaId error: NO];
}
ad.downloadDone = YES;
}
}
downloadSegment = 0;
}
}
// delegate back once all the main media segments have been downloaded.
if (reqUrlCount == 0) {
[self setDownloadComplete: mediaInfo];
state = DownloadState_Done;
// skip sending downloadFinish delegate if the media is marked as downloadDone
if (!bgDownloadMediaInfo.downloadDone) {
[delegate MediaDownloadDidFinish: bgDownloadMediaInfo.mediaId error: NO];
}
bgDownloadMediaInfo.downloadDone = YES;
[urlArr release];
[pathArr release];
[finalUrlArr release];
[finalPathArr release];
// invalidate the NSURL session once complete
[backgroundSession invalidateAndCancel];
}
}
- (void)URLSession: (NSURLSession *)session
task: (NSURLSessionTask *)task
didCompleteWithError: (NSError *)error
{
if (error) {
NSLog(#"Failure to download request url (%#) with error (%#)", task.originalRequest.URL, error);
}
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// save the total downloaded size
[self downloaderDidReceiveData:bytesWritten];
// enable the log only for debugging purpose.
// LogDebug(#"totalBytesExpectedToWrite %llu, totalBytesWritten %llu, %#", totalBytesExpectedToWrite, totalBytesWritten, downloadTask.currentRequest.URL);
}
With out this code(beginBackgroundTaskWithExpirationHandler) the download stops when the app is pushed into background.
// AppDelegate_Phone.m
- (void)applicationDidEnterBackground: (UIApplication *)application
{
NSLog(#"applicationDidEnterBackground");
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
}
Have you implemented application:handleEventsForBackgroundURLSession:completionHa‌​ndler: in your app delegate? That should save the completion handler and start background session with the specified identifier.
If you don't implement that method, your app will not be informed if the download finishes after the app has been suspended (or subsequently terminated in the course of normal app lifecycle). As a result, it might look like the download didn't finish, even though it did.
(As an aside, note that if the user force-quits the app, that not only terminates the download, but obviously won't inform your app that the download was terminated until the user manually restarts the app at some later point and your app re-instantiates the background session. This is a second-order concern that you might not worry about until you get the main background processing working, but it's something to be aware of.)
Also, your URLSessionDidFinishEventsForBackgroundURLSession: must call that saved completion handler (and dispatch this to the main queue).
Also, your design looks like it will issue only one request at a time. (I'd advise against that, but let's just assume it is as you've outlined above.) So, let's imagine that you have issued the first request and the app is suspended before it's done. Then, when the download is done, the app is restarted in the background and handleEventsForBackgroundURLSession is called. Let's assume you fixed that to make sure it restarts the background session so that the various delegate methods can be called. Make sure that when you issue that second request for the second download that you use the existing background session, not instantiating a new one. You can have only one background session per identifier. Bottom line, the instantiation of the background session should be decoupled from downloadWithURL:pathArr:mediaInfo:. Only instantiate a background session once.
Add "Required background modes" in your .plist
There, add the item "App downloads content from the network"

NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext

i am having a problem since days, i tryed all the solutions here in stackoverflow but anyone gives me a solution.
i am using core data and tableviewcontroller.
My CoreDataTableViewController its correct, i downloaded it from timroadnley and u use it in another projects.
Im getting an error : 'An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext'
The application loads correctly but when i press to go to the marksTableviewcontroller, it fails.
THE ERROR IS ON THIS LINE:
- (void)setupFetchedResultsController{
// 1 - Decide what Entity you want
NSString *entityName = #"Marks"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"subject"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil]; //IN THIS LILE IS THE ERROR, IN "REQUEST"
[self performFetch];
}
THIS IS MY APPDELEGATE:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
marksTVCon.managedObjectContext = self.managedObjectContext;
NSDictionary *defaultsDict = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], #"FirstLaunch", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDict];
NSUserDefaults *sharedDefaults = [NSUserDefaults standardUserDefaults];
if([sharedDefaults boolForKey:#"FirstLaunch"]) {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"MYAPP" message:#"Thanks for downloading, hope you love our app" delegate:nil cancelButtonTitle:#"Go app" otherButtonTitles:nil, nil];
[message show];
[sharedDefaults setBool:NO forKey:#"FirstLaunch"];
[sharedDefaults synchronize];
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
[self saveContext];
}
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#pragma mark - Core Data stack
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MYAPP" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MYAPP.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
#pragma mark - Application's Documents directory
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#end
If you need to ask for anyother peace of code pliz ask for it, I think the problem is in there, but im not pretty Sure.

Setting variable with Grand Central Dispatch not retrievable

I'm trying to set and a NSURL using grand central dispatch, however it appears that the variable is set and accessible until you try to access it outside of the grand central dispatch block.
-(void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue,^{
self.ubiquitousURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSLog(#"ubiq inside: %#", self.ubiquitousURL);
if (self.ubiquitousURL) {
self.iCloudDocURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#Documents", self.ubiquitousURL]];
self.iCloudDocString = [self.iCloudDocURL absoluteString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadFiles) name: UIApplicationDidBecomeActiveNotification object:nil];
} else {
/* change to the main queue if you want to do something with the UI. For example: */
dispatch_async(dispatch_get_main_queue(),^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enable iCloud" message:nil delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
});
}
});
NSLog(#"ubiq outside: %#", self.ubiquitousURL);
}
The first NSLog which, starts with ubiq inside returns the correct URL, while ubiq outside returns NULL. I'm using ARC, so no need to mention memory or anything similar... this is a GCD problem.
Do you know why self.ubiquitousURL is not accessible outside of the GCD block? Thanks.
You are making async call. So this line NSLog(#"ubiq outside: %#", self.ubiquitousURL); will get executed whether or not your code inside backgroundQueue is done.
You would see the outside log first then inside log.
dispatch_async means "run this later". Thus, the code inside the block doesn't run immediately; it runs at some later time, after the "outside" NSLog call has already been run. If you were to, for instance, put sleep(5) before the NSLog call, you would probably see the value. (You shouldn't really do that in the actual code, though; it would basically freeze the app for five seconds.)
If you want to run more code on the main queue after you've set that property, do something like this:
dispatch_async(backgroundQueue,^{
self.ubiquitousURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSLog(#"ubiq inside: %#", self.ubiquitousURL);
if (self.ubiquitousURL) {
self.iCloudDocURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#Documents", self.ubiquitousURL]];
self.iCloudDocString = [self.iCloudDocURL absoluteString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loadFiles) name: UIApplicationDidBecomeActiveNotification object:nil];
// ************** NEW HOTNESS HERE **************
dispatch_async(dispatch_get_main_queue(),^{
NSLog(#"ubiq outside: %#", self.ubiquitousURL);
});
} else {
/* change to the main queue if you want to do something with the UI. For example: */
dispatch_async(dispatch_get_main_queue(),^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please enable iCloud" message:nil delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
});
}
});
Replace that NSLog line with a method call to do actual work if that's what you want to do once you've retrieved the iCloud URL.

ASIHttp Synchronous request is running delegate methods after returning

I am trying to download a file from a server. My code is following. In didFinishLaunchingWithOptions method, I create a new thread using detachNewThreadSelector which runs the following code.
NSString *destPath = [self.home_dir_path stringByAppendingPathComponent:[NSString stringWithFormat:#"_%#",content_data_file_name]];
[ContentBO downloadFile:destPath content_name:content_data_file_name];
if([self updatesAvailable]){
//update content
}else{
//launch app
}
My code for downloadFile is:
#try{
NSString *url = [NSString stringWithFormat:#"%#/%#",ServerURL,content_name];
NSLog(#"downloading URL is: %#",url);
self.request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
[self.request setRequestMethod:#"GET"];
[self.request setDownloadDestinationPath:destFilePath];
NSLog(#"destination path is: %#",destFilePath);
[self.request setTimeOutSeconds:30];
[self.request setDelegate:self];
[self.request startSynchronous];
NSError *error = [self.request error];
NSData *receivedData = nil;
if (!error) {
isSuccess = YES;
self.responseStr = [request responseString];
receivedData = [NSData dataWithData:[self.request responseData]];
}
else {
isSuccess = NO;
NSLog(#"The following error occurred: %#", error);
}
}#catch(NSException *e){
NSLog(#"exception occured.");
}
What my understanding about synchronous call is that this is a blocking call and control should not go below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
until control is out of requestFinished method of ASIHTTPRequestDelegate. In my case what happening is that the control is simultaneously executing code in requestFinished and below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
But I don't want the control to go below [ContentBO downloadFile...] before coming out of requestFinished method.
The requestFinished delegate call is run on the main thread asynchronously, and your code is not running on the main thread, so it is expected that both would run at the same time.
However, as you are using synchronous requests why not remove the contents of requestFinished and put the code after the 'startSyncronous' line? You are guaranteed the request has finished when startSynchronous returns.
In one of my projects the app had to do heavy server side data syncing. In that process one operation should had start after the successful execution of it's previous process and I was using ASIHttp synchronous requests in that. I was facing the same issue you mentioned, so to tackle it I used NSCondiiton. All it requires that you lock the thread after you call:
[self.request startSynchronous];. When the requests delegate method is called after the exection of the request, issue a signal command, and the next line of the code after the thread lock statement will be executed. Here is a rough example:
//declare a pointer to NSCondition in header file:
NSCondition *threadlock;
-(id) init
{
threadlock = [[NSCondition alloc] init]; //release it in dealloc
}
-(void)downLoadFile
{
[thread lock];
//your request code
[self.request setDidFinishSelector:#selector(downLoadFileRequestDone:)];
[self.request setDidFailSelector:#selector(downLoadFileRequestWentWrong:)];
[self.request startSynchronous];
[thread wait];
//the lines here will be executed after you issue a signal command in the request delegate method
[thread unlock];
}
-(void) downLoadFileRequestDone:(ASIHTTPRequest *)request
{
[thread lock];
//perform desire functionality
//when you are done call:
[thread signal];
[thread unlock];
}
It worked perfect for me... hope it will help.

Testing controller method with OCMock and Core Data

I am just grasping the concepts of TDD and mocking, and am running into an issue in terms of how to properly. I have a sheet that drops down and lets a user create a new core data object and save it to the data store. I am not sure if I am taking the best approach to testing it.
- (IBAction)add:(id)sender
{
NSString *itemName = [self.itemNameTextField stringValue];
SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
newItem.name = itemName;
NSError *error = nil;
BOOL canSaveNewItem = [[self managedObjectContext] save:&error];
if (!canSaveNewItem)
{
[NSApp presentError:error];
}
[self clearFormFields]; // Private method that clears text fields, disables buttons
[NSApp endSheet:[self window] returnCode:NSOKButton];
}
I'm trying to write two test methods to test this: one that tests the scenario where the managed object can't save and one where it successfully saves.
#interface SGAddItemWindowControllerTests : SGTestCase
{
#private
SGAddItemWindowController *addItemWindowController;
id mockApp;
id mockNameField;
}
- (void)setUp
{
mockNameField = [OCMockObject mockForClass:[NSTextField class]];
mockApp = [OCMockObject mockForClass:[NSApplication class]];
addItemWindowController = [[BLAddItemWindowController alloc] init];
[addItemWindowController setValue:mockNameField forKey:#"itemNameTextField"];
}
- (void)testAddingNewItemFromSheetFailed
{
// Setup
NSString *fakeName = #"";
[[[mockNameField expect] andReturn:fakeName] stringValue];
[[mockApp expect] presentError:[OCMArg any]];
// Execute
[addItemWindowController add:nil];
// Verify
[mockApp verify];
}
- (void)testAddingNewItemFromSheetSucceeds
{
// Setup
NSString *fakeName = #"Item Name";
[[[mockNameField expect] andReturn:fakeName] stringValue];
[[mockApp expect] endSheet:[OCMArg any] returnCode:NSOKButton];
// Execute
[addItemWindowController add:nil];
// Verify
[mockApp verify];
[mockNameField verify];
}
#end
Here are the issues I know I have, but am not sure how to work out:
I am not sure how to handle dealing with the managed object context in terms of the test. Should I bring up the entire core data stack or just create a mock of NSManagedObjectContext?
The idea of just setting the text field values as the way to trigger the if statement seems wrong. Ideally I think I should stub out the save: method and return YES or NO, but given question 1 I'm not sure about the Core Data aspects of it all.
I think I'm on the right track, but I could use a second opinion on how to tackle my issues and set me on the right path for testing the code snippet.
Justin,
What I do for question #1 is to create an actual NSManagedObjectContext but create an im-memory persistence store. Nothing hits the disk and I test the CoreData version of the truth.
I have a MWCoreDataTest class (extends in my case GTMTestCase) that builds the moc and initializes the persistence store
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSPersistentStoreCoordinator*)persistentStoreCoordinator;
{
if (persistentStoreCoordinator) return persistentStoreCoordinator;
NSError* error = nil;
NSManagedObjectModel *mom = [self managedObjectModel];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:mom];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:nil
options:nil
error:&error]) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
return persistentStoreCoordinator;
}
WRT #2, I think that's ok - if you plan on testing more than one behavior in the class, move the
[addItemWindowController setValue:mockNameField forKey:#"itemNameTextField"];
to the testAdding.. method
If you solve #1, then you could just set the itemNameText field to nil and your save validation would trigger.
WRT #3, I would validate that building a mock on NSApp === building a mock on NSApplication
What is that you want to test? Do you want to test that Core Data does the saving or not? Or, do you want to test that your application responds correctly to the result of the call to CoreData?
Either way I think you should extract a method that performs the saving along the lines of:
-(BOOL)saveNewItem:(NSString *)itemName error:(NSError **)error {
SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
newItem.name = itemName;
NSError *error = nil;
return[[self managedObjectContext] save:&error];
}
- (IBAction)add:(id)sender {
NSString *itemName = [self.itemNameTextField stringValue];
NSError *error = nil;
BOOL canSaveNewItem = [self saveNewItem:itemName error:&error];
if (!canSaveNewItem) {
[NSApp presentError:error];
}
[self clearFormFields]; // Private method that clears text fields, disables buttons
[NSApp endSheet:[self window] returnCode:NSOKButton];
}
This way you can test that Core Data saving works as expected by settings up an in memory store and not have to care about the business logic. You should also be able to override or mock the result of this method for testing the business logic.
I would perhaps even move all the Core Data stuff to a separate class that would encapsulate the interaction for easier mocking.