From c2fa8c48591a759b4ebf0f3f00746262cd1f66c6 Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Fri, 14 Dec 2012 12:18:34 +1100 Subject: [PATCH] All deprecated and non-deprecated methods have tests to ensure their function --- .../NSManagedObjectContext+MagicalSaves.h | 10 +- .../NSManagedObjectContext+MagicalSaves.m | 76 +-- MagicalRecord/Core/MagicalRecord+Actions.h | 2 +- MagicalRecord/Core/MagicalRecord+Actions.m | 8 +- .../MagicalRecord+ActionsSpec.m | 261 +++++--- .../NSManagedObjectContext+MagicalSavesSpec.m | 564 ++++++++++++++++-- 6 files changed, 726 insertions(+), 195 deletions(-) diff --git a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h index 4815c6c..6b75c11 100644 --- a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h +++ b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.h @@ -32,14 +32,14 @@ typedef void (^MRSaveCompletionHandler)(BOOL success, NSError *error); * The following methods are deprecated, but remain in place for backwards compatibility until the next major version (3.x) */ - (void) MR_save __attribute__((deprecated)); -- (void) MR_saveWithErrorCallback:(void(^)(NSError *))errorCallback __attribute__((deprecated)); +- (void) MR_saveWithErrorCallback:(void(^)(NSError *error))errorCallback __attribute__((deprecated)); - (void) MR_saveInBackgroundCompletion:(void (^)(void))completion __attribute__((deprecated)); -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback __attribute__((deprecated)); -- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); +- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback __attribute__((deprecated)); +- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); - (void) MR_saveNestedContexts __attribute__((deprecated)); -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback __attribute__((deprecated)); -- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); +- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback __attribute__((deprecated)); +- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion __attribute__((deprecated)); @end diff --git a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m index 62a6992..c7ea37a 100644 --- a/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m +++ b/MagicalRecord/Categories/NSManagedObjectContext/NSManagedObjectContext+MagicalSaves.m @@ -50,6 +50,8 @@ } MRLog(@"→ Saving %@", [self MR_description]); + MRLog(@"→ Save Parents? %@", @(saveParentContexts)); + MRLog(@"→ Save Synchronously? %@", @(syncSave)); id saveBlock = ^{ NSError *error = nil; @@ -102,46 +104,17 @@ #pragma mark - Deprecated methods // These methods will be removed in MagicalRecord 3.0 -- (void)MR_saveNestedContexts; -{ - [self MR_saveToPersistentStoreWithCompletion:nil]; -} - -- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback; -{ - [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { - if (!success) { - if (errorCallback) { - errorCallback(error); - } - } - }]; -} - -- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; -{ - [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { - if (success) { - if (completion) { - completion(); - } - } else { - if (errorCallback) { - errorCallback(error); - } - } - }]; -} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)MR_save; { [self MR_saveToPersistentStoreAndWait]; } -- (void)MR_saveWithErrorCallback:(void (^)(NSError *))errorCallback __attribute__((deprecated)); - +- (void)MR_saveWithErrorCallback:(void (^)(NSError *error))errorCallback; { - [self MR_saveWithOptions:MRSaveSynchronously completion:^(BOOL success, NSError *error) { + [self MR_saveWithOptions:MRSaveSynchronously|MRSaveParentContexts completion:^(BOOL success, NSError *error) { if (!success) { if (errorCallback) { errorCallback(error); @@ -161,7 +134,7 @@ }]; } -- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback; +- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback; { [self MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { if (!success) { @@ -172,7 +145,7 @@ }]; } -- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *))errorCallback completion:(void (^)(void))completion; +- (void)MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion; { [self MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { if (success) { @@ -187,4 +160,37 @@ }]; } +- (void)MR_saveNestedContexts; +{ + [self MR_saveToPersistentStoreWithCompletion:nil]; +} + +- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback; +{ + [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + if (!success) { + if (errorCallback) { + errorCallback(error); + } + } + }]; +} + +- (void)MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion; +{ + [self MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + if (success) { + if (completion) { + completion(); + } + } else { + if (errorCallback) { + errorCallback(error); + } + } + }]; +} + +#pragma clang diagnostic pop // ignored "-Wdeprecated-implementations" + @end diff --git a/MagicalRecord/Core/MagicalRecord+Actions.h b/MagicalRecord/Core/MagicalRecord+Actions.h index 7926ac3..c11759d 100644 --- a/MagicalRecord/Core/MagicalRecord+Actions.h +++ b/MagicalRecord/Core/MagicalRecord+Actions.h @@ -39,6 +39,6 @@ /* If you want to reuse the context on the current thread, use this method. */ -+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler __attribute__((deprecated)); ++ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *error))errorHandler __attribute__((deprecated)); @end diff --git a/MagicalRecord/Core/MagicalRecord+Actions.m b/MagicalRecord/Core/MagicalRecord+Actions.m index c4f7bf9..2e2d4d2 100644 --- a/MagicalRecord/Core/MagicalRecord+Actions.m +++ b/MagicalRecord/Core/MagicalRecord+Actions.m @@ -78,6 +78,9 @@ #pragma mark - Deprecated methods +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block { [[self class] saveWithBlock:block completion:nil]; @@ -103,7 +106,7 @@ }]; } -+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *))errorHandler; ++ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *error))errorHandler; { NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread]; @@ -113,7 +116,6 @@ } [localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { - if (success) { if (completion) { completion(); @@ -128,4 +130,6 @@ }]; } +#pragma clang diagnostic pop // ignored "-Wdeprecated-implementations" + @end diff --git a/Project Files/Mac Unit Tests/MagicalRecord+ActionsSpec.m b/Project Files/Mac Unit Tests/MagicalRecord+ActionsSpec.m index 219231e..17eaea4 100644 --- a/Project Files/Mac Unit Tests/MagicalRecord+ActionsSpec.m +++ b/Project Files/Mac Unit Tests/MagicalRecord+ActionsSpec.m @@ -16,213 +16,288 @@ describe(@"MagicalRecord", ^{ // Occurs after each enclosed "it" block [MagicalRecord cleanUp]; }); - + context(@"synchronous save action", ^{ it(@"should save", ^{ __block NSManagedObjectID *objectId; - __block NSManagedObject *fetchedObject; - + [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; }]; - + [[objectId should] beNonNil]; - - fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + [[fetchedObject should] beNonNil]; [[@([fetchedObject hasChanges]) should] beFalse]; }); }); - + context(@"asynchronous save action", ^{ it(@"should call completion block", ^{ __block BOOL completionBlockCalled = NO; - + [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { [SingleEntityWithNoRelationships MR_createInContext:localContext]; } completion:^(BOOL success, NSError *error) { // Ignore the success state — we only care that this block is executed completionBlockCalled = YES; }]; - + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; }); - + it(@"should save", ^{ __block BOOL saveSuccessState = NO; __block NSManagedObjectID *objectId; __block NSManagedObject *fetchedObject; - + [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; } completion:^(BOOL success, NSError *error) { saveSuccessState = success; - fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; + fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; }]; - + [[expectFutureValue(@(saveSuccessState)) shouldEventually] beTrue]; [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; }); }); - + context(@"current thread save action", ^{ context(@"running synchronously", ^{ it(@"should save", ^{ __block NSManagedObjectID *objectId; - + [MagicalRecord saveUsingCurrentThreadContextWithBlockAndWait:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; }]; - + [[objectId should] beNonNil]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + [[fetchedObject should] beNonNil]; [[@([fetchedObject hasChanges]) should] beFalse]; }); }); - + context(@"running asynchronously", ^{ it(@"should call completion block", ^{ __block BOOL completionBlockCalled = NO; - + [MagicalRecord saveUsingCurrentThreadContextWithBlock:^(NSManagedObjectContext *localContext) { [SingleEntityWithNoRelationships MR_createInContext:localContext]; } completion:^(BOOL success, NSError *error) { // Ignore the success state — we only care that this block is executed completionBlockCalled = YES; }]; - + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; }); - + it(@"should save", ^{ + __block BOOL saveSuccessState = NO; + __block NSError *saveError; __block NSManagedObjectID *objectId; - + [MagicalRecord saveUsingCurrentThreadContextWithBlock:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; } completion:^(BOOL success, NSError *error) { - [[@(success) should] beTrue]; + saveSuccessState = success; + saveError = error; }]; + [[expectFutureValue(@(saveSuccessState)) shouldEventually] beTrue]; + [[expectFutureValue(saveError) shouldEventually] beNil]; [[expectFutureValue(objectId) shouldEventually] beNonNil]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; }); }); }); - - context(@"deprecated method", ^{ - context(@"simple save", ^{ - it(@"should save", ^{ - NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_defaultContext]; - NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntity]; - - [[@([inserted hasChanges]) should] beTrue]; - - [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; - NSManagedObjectID *objectId = [inserted objectID]; - - [[objectId should] beNonNil]; - - [[[managedObjectContext should] receive] MR_save]; - [managedObjectContext MR_save]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - - [[fetchedObject should] beNonNil]; - [[@([fetchedObject hasChanges]) should] beFalse]; - }); - }); - - context(@"save action", ^{ + + + // We're testing for deprecated method function — ignore the warnings +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + + context(@"deprecated method", ^{ + context(@"saveWithBlock:", ^{ it(@"should save", ^{ __block NSManagedObjectID *objectId; - + [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; }]; - + [[objectId should] beNonNil]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; [[objectId should] beNonNil]; [[fetchedObject should] beNonNil]; [[@([fetchedObject hasChanges]) should] beFalse]; }); }); - - context(@"background save action", ^{ - it(@"should call completion block", ^{ - __block BOOL completionBlockCalled = NO; - - [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) { - [SingleEntityWithNoRelationships MR_createInContext:localContext]; - } completion:^{ - // Ignore the success state — we only care that this block is executed - completionBlockCalled = YES; - }]; - - [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; - }); - + + context(@"saveInBackgroundWithBlock:", ^{ it(@"should save", ^{ __block NSManagedObjectID *objectId; - __block NSManagedObject *fetchedObject; - + [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) { NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; - + [[@([inserted hasChanges]) should] beTrue]; - + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; objectId = [inserted objectID]; - } completion:^{ - [[objectId should] beNonNil]; - - fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; }]; - + + [[expectFutureValue(objectId) shouldEventually] beNonNil]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + [[expectFutureValue(objectId) shouldEventually] beNonNil]; [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; }); }); - + context(@"saveInBackgroundWithBlock:completion:", ^{ + it(@"should call completion block", ^{ + __block BOOL completionBlockCalled = NO; + + [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) { + [SingleEntityWithNoRelationships MR_createInContext:localContext]; + } completion:^{ + completionBlockCalled = YES; + }]; + + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + }); + + it(@"should save", ^{ + __block NSManagedObjectID *objectId; + __block NSManagedObject *fetchedObject; + + [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) { + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; + + [[@([inserted hasChanges])should] beTrue]; + + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + objectId = [inserted objectID]; + } completion:^{ + [[objectId should] beNonNil]; + + fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + }]; + + [[expectFutureValue(objectId) shouldEventually] beNonNil]; + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + }); + }); + + context(@"saveInBackgroundUsingCurrentContextWithBlock:completion:errorHandler:", ^{ + + context(@"should call", ^{ + __block BOOL completionBlockCalled = NO; + __block BOOL errorBlockCalled = NO; + + afterEach(^{ + completionBlockCalled = NO; + errorBlockCalled = NO; + }); + + it(@"completion block", ^{ + [MagicalRecord saveInBackgroundUsingCurrentContextWithBlock:^(NSManagedObjectContext *localContext) { + [SingleEntityWithNoRelationships MR_createInContext:localContext]; + } completion:^{ + completionBlockCalled = YES; + } errorHandler:^(NSError *error) { + errorBlockCalled = YES; + }]; + + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + [[expectFutureValue(@(errorBlockCalled)) shouldEventually] beFalse]; + }); + + it(@"error handler when there is an error", ^{ + [MagicalRecord saveInBackgroundUsingCurrentContextWithBlock:^(NSManagedObjectContext *localContext) { + // Don't make any changes so that an error is triggered + } completion:^{ + completionBlockCalled = YES; + } errorHandler:^(NSError *error) { + errorBlockCalled = YES; + }]; + + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beFalse]; + [[expectFutureValue(@(errorBlockCalled)) shouldEventually] beTrue]; + }); + }); + + it(@"should save", ^{ + __block NSError *saveError; + __block NSManagedObjectID *objectId; + __block NSManagedObject *fetchedObject; + + [MagicalRecord saveInBackgroundUsingCurrentContextWithBlock:^(NSManagedObjectContext *localContext) { + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:localContext]; + + [[@([inserted hasChanges])should] beTrue]; + + [localContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + objectId = [inserted objectID]; + } completion:^{ + [[objectId should] beNonNil]; + + fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + } errorHandler:^(NSError *error) { + saveError = error; + }]; + + [[expectFutureValue(objectId) shouldEventually] beNonNil]; + [[expectFutureValue(saveError) shouldEventually] beNil]; + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + }); + }); }); + +#pragma clang diagnostic pop // ignored "-Wdeprecated-declarations" + }); SPEC_END diff --git a/Project Files/Mac Unit Tests/NSManagedObjectContext+MagicalSavesSpec.m b/Project Files/Mac Unit Tests/NSManagedObjectContext+MagicalSavesSpec.m index fed5ef3..fee20f9 100644 --- a/Project Files/Mac Unit Tests/NSManagedObjectContext+MagicalSavesSpec.m +++ b/Project Files/Mac Unit Tests/NSManagedObjectContext+MagicalSavesSpec.m @@ -6,73 +6,519 @@ SPEC_BEGIN(NSManagedObjectContextMagicalSavesSpec) describe(@"NSManagedObjectContext+MagicalSaves", ^{ - beforeEach(^{ - // Occurs before each enclosed "it" block + beforeEach (^{ + // Occurs before each enclosed "it" block [MagicalRecord setDefaultModelFromClass:[self class]]; [MagicalRecord setupCoreDataStackWithInMemoryStore]; - }); - - afterEach(^{ - // Occurs after each enclosed "it" block + }); + + afterEach (^{ + // Occurs after each enclosed "it" block [MagicalRecord cleanUp]; - }); - - context(@"saving synchronously", ^{ - it(@"should save", ^{ - NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]]; - NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; - - [[@([inserted hasChanges]) should] beTrue]; - - [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; - NSManagedObjectID *objectId = inserted.objectID; - - [managedObjectContext MR_saveToPersistentStoreAndWait]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - - [[fetchedObject should] beNonNil]; - [[@([fetchedObject hasChanges]) should] beFalse]; - }); - }); - - context(@"saving asynchronously", ^{ - it(@"should call completion block", ^{ - __block BOOL completionBlockCalled = NO; - - NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_contextForCurrentThread]; - - [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; - - [managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { - completionBlockCalled = YES; - }]; - - [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + + context(@"be able to save to self only", ^{ + __block NSManagedObjectContext *managedObjectContext; + + beforeEach (^{ + managedObjectContext = [NSManagedObjectContext MR_context]; }); - - it(@"should save", ^{ - __block NSManagedObjectID *objectId; - NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]]; - NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; - - [[@([inserted hasChanges]) should] beTrue]; - - [managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + + afterEach (^{ + [managedObjectContext reset]; + managedObjectContext = nil; + }); + + context(@"synchronously", ^{ + it(@"should save", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; - objectId = inserted.objectID; - }]; - - [[expectFutureValue(objectId) shouldEventually] beNonNil]; - - NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectWithID:objectId]; - - [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; - [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + NSManagedObjectID *objectId = [inserted objectID]; + + [managedObjectContext MR_saveOnlySelfAndWait]; + + NSManagedObject *rootFetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [rootFetchedObject shouldBeNil]; + [[@([rootFetchedObject hasChanges]) should] beFalse]; + + rootFetchedObject = [managedObjectContext objectRegisteredForID:objectId]; + + [rootFetchedObject shouldNotBeNil]; + [[@([rootFetchedObject hasChanges]) should] beFalse]; + }); + }); + + context(@"asynchronously", ^{ + it(@"should call completion block", ^{ + __block BOOL completionBlockCalled = NO; + + [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [managedObjectContext MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { + completionBlockCalled = YES; + }]; + + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + }); + + it(@"should save", ^{ + __block NSManagedObjectID *objectId; + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) { + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + objectId = [inserted objectID]; + }]; + + [[expectFutureValue(objectId) shouldEventually] beNonNil]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + + fetchedObject = [managedObjectContext objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + }); }); }); - + + context(@"be able to save to the persistent store", ^{ + __block NSManagedObjectContext *managedObjectContext; + + beforeEach (^{ + managedObjectContext = [NSManagedObjectContext MR_context]; + }); + + afterEach (^{ + [managedObjectContext reset]; + managedObjectContext = nil; + }); + + context(@"synchronously", ^{ + it(@"and should save", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([managedObjectContext hasChanges]) should] beTrue]; + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + [managedObjectContext MR_saveToPersistentStoreAndWait]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [fetchedObject shouldNotBeNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + }); + + context(@"asynchronously", ^{ + it(@"and should call completion block", ^{ + __block BOOL completionBlockCalled = NO; + + [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([managedObjectContext hasChanges]) should] beTrue]; + + [managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + completionBlockCalled = YES; + }]; + + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + }); + + it(@"and should save", ^{ + __block NSManagedObjectID *objectId; + + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([managedObjectContext hasChanges]) should] beTrue]; + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + objectId = [inserted objectID]; + }]; + + [[expectFutureValue(objectId) shouldEventually] beNonNil]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + }); + }); + }); + + context(@"be able to save with options", ^{ + __block NSManagedObjectContext *managedObjectContext; + __block NSManagedObjectID *permanentObjectID; + __block NSManagedObject *insertedObject; + + beforeEach (^{ + managedObjectContext = [NSManagedObjectContext MR_context]; + insertedObject = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + [managedObjectContext obtainPermanentIDsForObjects:@[insertedObject] error:nil]; + }); + + afterEach (^{ + [managedObjectContext reset]; + + permanentObjectID = nil; + insertedObject = nil; + managedObjectContext = nil; + }); + + context(@"synchronously", ^{ + beforeEach(^{ + permanentObjectID = [insertedObject objectID]; + [permanentObjectID shouldNotBeNil]; + }); + + specify (^{ + [[@([managedObjectContext hasChanges]) should] beTrue]; + [[@([insertedObject hasChanges]) should] beTrue]; + }); + + it(@"to self only", ^{ + [managedObjectContext MR_saveWithOptions:MRSaveSynchronously completion:^(BOOL success, NSError *error) { + [[@(success) should] beTrue]; + [error shouldBeNil]; + }]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:permanentObjectID]; + [fetchedObject shouldBeNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + + it(@"to persistent store", ^{ + [managedObjectContext MR_saveWithOptions:MRSaveSynchronously | MRSaveParentContexts completion:^(BOOL success, NSError *error) { + [[@(success) should] beTrue]; + [error shouldBeNil]; + }]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:permanentObjectID]; + [fetchedObject shouldNotBeNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + }); + + context(@"asynchronously", ^{ + it(@"to self only", ^{ + [managedObjectContext MR_saveWithOptions:0 completion:^(BOOL success, NSError *error) { + [[@(success) should] beTrue]; + [error shouldBeNil]; + + [managedObjectContext obtainPermanentIDsForObjects:@[insertedObject] error:nil]; + permanentObjectID = [insertedObject objectID]; + }]; + + [[expectFutureValue(permanentObjectID) shouldEventually] beNonNil]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:permanentObjectID]; + [[expectFutureValue(fetchedObject) shouldEventually] beNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + + it(@"to persistent store", ^{ + [managedObjectContext MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL success, NSError *error) { + [[@(success) should] beTrue]; + [error shouldBeNil]; + + [managedObjectContext obtainPermanentIDsForObjects:@[insertedObject] error:nil]; + permanentObjectID = [insertedObject objectID]; + }]; + + [[expectFutureValue(permanentObjectID) shouldEventually] beNonNil]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:permanentObjectID]; + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + }); + }); + + // We're testing for deprecated method function — ignore the warnings +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + + context(@"deprecated method", ^{ + __block NSManagedObjectContext *managedObjectContext; + + beforeEach (^{ + managedObjectContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]]; + }); + + afterEach (^{ + [managedObjectContext reset]; + managedObjectContext = nil; + }); + + context(@"MR_save", ^{ + it(@"should save", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + [managedObjectContext MR_save]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [fetchedObject shouldNotBeNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + }); + + context(@"MR_saveWithErrorCallback", ^{ + it(@"should call error handler on errors", ^{ + __block BOOL errorHandlerCalled = NO; + + [managedObjectContext MR_saveWithErrorCallback:^(NSError *error) { + errorHandlerCalled = YES; + }]; + + [[@(errorHandlerCalled) should] beTrue]; + }); + + it(@"should save", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + __block BOOL errorHandlerCalled = NO; + __block NSError *saveError; + + [managedObjectContext MR_saveWithErrorCallback:^(NSError *error) { + saveError = error; + errorHandlerCalled = YES; + }]; + + [saveError shouldBeNil]; + [[@(errorHandlerCalled) should] beFalse]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [fetchedObject shouldNotBeNil]; + [[@([fetchedObject hasChanges]) should] beFalse]; + }); + }); + + context(@"MR_saveInBackgroundErrorHandler", ^{ + it(@"should call error handler on errors", ^{ + __block BOOL errorHandlerCalled = NO; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beTrue]; + }); + + it(@"should save to self, and be present in parent context", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + __block BOOL errorHandlerCalled = NO; + __block NSError *saveError; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + saveError = error; + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(@([inserted hasChanges])) shouldEventually] beFalse]; + + // There should be no errors + [[expectFutureValue(saveError) shouldEventually] beNil]; + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beFalse]; + + // Retrieve the object from the root saving context, and check that it's valid + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + + // Check that the object has been passed up to the parent context, but that the fetched object has unsaved changes + fetchedObject = [[managedObjectContext parentContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beTrue]; + }); + }); + + context(@"MR_saveInBackgroundErrorHandler", ^{ + it(@"should call completion block", ^{ + __block BOOL completionBlockCalled = NO; + __block BOOL errorHandlerCalled = NO; + + [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + errorHandlerCalled = YES; + } completion:^{ + completionBlockCalled = YES; + }]; + + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beFalse]; + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beTrue]; + }); + + it(@"should call error handler on errors", ^{ + __block BOOL completionBlockCalled = NO; + __block BOOL errorHandlerCalled = NO; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + errorHandlerCalled = YES; + } completion:^{ + completionBlockCalled = YES; + }]; + + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beTrue]; + [[expectFutureValue(@(completionBlockCalled)) shouldEventually] beFalse]; + }); + + it(@"should save to self, and be present in parent context", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + __block BOOL errorHandlerCalled = NO; + __block NSError *saveError; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + saveError = error; + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(@([inserted hasChanges])) shouldEventually] beFalse]; + + // There should be no errors + [[expectFutureValue(saveError) shouldEventually] beNil]; + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beFalse]; + + // Retrieve the object from the root saving context, and check that it's valid + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + + // Check that the object has been passed up to the parent context, but that the fetched object has unsaved changes + fetchedObject = [[managedObjectContext parentContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beTrue]; + }); + }); + + context(@"MR_saveNestedContextsErrorHandler", ^{ + it(@"should call error handler on errors", ^{ + __block BOOL errorHandlerCalled = NO; + + [managedObjectContext MR_saveNestedContextsErrorHandler:^(NSError *error) { + errorHandlerCalled = YES; + }]; + + [[@(errorHandlerCalled) should] beTrue]; + }); + + it(@"should save", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + __block BOOL errorHandlerCalled = NO; + __block NSError *saveError; + + [managedObjectContext MR_saveNestedContextsErrorHandler:^(NSError *error) { + saveError = error; + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(saveError) shouldEventually] beNil]; + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beFalse]; + + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + }); + }); + + context(@"MR_saveInBackgroundErrorHandler", ^{ + it(@"should call error handler on errors", ^{ + __block BOOL errorHandlerCalled = NO; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beTrue]; + }); + + it(@"should save to self, and be present in parent context", ^{ + NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createInContext:managedObjectContext]; + + [[@([inserted hasChanges]) should] beTrue]; + + [managedObjectContext obtainPermanentIDsForObjects:@[inserted] error:nil]; + NSManagedObjectID *objectId = [inserted objectID]; + + __block BOOL errorHandlerCalled = NO; + __block NSError *saveError; + + [managedObjectContext MR_saveInBackgroundErrorHandler:^(NSError *error) { + saveError = error; + errorHandlerCalled = YES; + }]; + + [[expectFutureValue(@([inserted hasChanges])) shouldEventually] beFalse]; + + // There should be no errors + [[expectFutureValue(saveError) shouldEventually] beNil]; + [[expectFutureValue(@(errorHandlerCalled)) shouldEventually] beFalse]; + + // Retrieve the object from the root saving context, and check that it's valid + NSManagedObject *fetchedObject = [[NSManagedObjectContext MR_rootSavingContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beFalse]; + + // Check that the object has been passed up to the parent context, but that the fetched object has unsaved changes + fetchedObject = [[managedObjectContext parentContext] objectRegisteredForID:objectId]; + + [[expectFutureValue(fetchedObject) shouldEventually] beNonNil]; + [[expectFutureValue(@([fetchedObject hasChanges])) shouldEventually] beTrue]; + }); + }); + }); + +#pragma clang diagnostic pop // ignored "-Wdeprecated-declarations" }); SPEC_END