RestKit CoreData hangs during unit test

2019-05-30 15:27发布

问题:

I am in the process of trying to get a test working for a service class that uses Restkit to perform various CoreData and JSON mapping operations related to the needs of our application. The service works fine when run via an iphone simulator deployment, but hangs when run via the context of a unit test.

It looks to be related to thread usage in Restkit as I have been able to narrow it down to the following class and method call. Basically, the performBlockAndWait never returns. I am very new to the objective c world (not to development in general) so any help would be greatly appreciated.

Restkit Class: RKFetchRequestManagedObjectCache

Method:

- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity
                attributeValues:(NSDictionary *)attributeValues
         inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext

...

    // test hangs on this fetch call
    [managedObjectContext performBlockAndWait:^{
        objects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    }];

I am setting up my CoreData test stack with the following:

NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@"EventServiceTests")];
NSLog(@"Found bundle: %@", bundle);

NSString *bundlePath = [bundle pathForResource:@"EventDataModel" ofType:@"momd"];
NSLog(@"Creating model from path: %@", bundlePath);

NSURL *momURL = [NSURL URLWithString:bundlePath];
NSLog(@"URL for model: %@", momURL);

NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];

    NSLog(@"Initializing the Core Data stack...");
    [managedObjectStore createPersistentStoreCoordinator];

    NSString* dataStorePath = [RKApplicationDataDirectory() stringByAppendingPathComponent: @"EventDataModel.dat"];
    NSLog(@"Persistent store file path: %@", dataStorePath);

    NSURL *storeUrl = [NSURL fileURLWithPath: dataStorePath];

    if (![managedObjectStore.persistentStoreCoordinator addPersistentStoreWithType:NSBinaryStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
        NSLog(@"Issue creating persitent store: %2@", error);
    }

    NSAssert(managedObjectStore.persistentStoreCoordinator.persistentStores, @"Failed to add persistent store: %@", error);

    [managedObjectStore createManagedObjectContexts];

    NSLog(@"Setting the default store shared instance to: %@", managedObjectStore);
    [RKManagedObjectStore setDefaultStore:managedObjectStore];

NSLog(@"Configuring the object manager...");
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://eventconsole.eng.techtarget.com/"]];
objectManager.managedObjectStore = managedObjectStore;

NSLog(@"Setting shared manager instance to: %@", objectManager);
[RKObjectManager setSharedManager:objectManager];

Then executing the request operation using:

NSString* url = UPCOMING_EVENTS_URL_PATH;
NSLog(@"Attempting to get upcoming events from url: %@", url);
[[RKObjectManager sharedManager] getObjectsAtPath:url parameters:nil
        success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
            NSLog(@"Successfully loaded %@ upcoming events",
            [NSString stringWithFormat:@"%ld", (unsigned long)[mappingResult count]] );
            returnVal = TRUE;
        }
        failure:^(RKObjectRequestOperation *operation, NSError *error) {
            NSLog(@"Error loading upcoming events: %@", error);
            returnVal = FALSE;
        }
 ];

and the actual test code:

NSLog(@"Executing testLoadAttendees...");
[_eventService loadAttendees:@"2269"];
[NSThread sleepForTimeInterval:5.0f];
NSOperationQueue* queue = [RKObjectRequestOperation responseMappingQueue];
[queue waitUntilAllOperationsAreFinished];

回答1:

I figured out a solution using one of the utility test classes provided by RestKit.

RKTestNotificationObserver *observer =
    [RKTestNotificationObserver 
         notificationObserverForName:RKObjectRequestOperationDidFinishNotification
                              object:nil];
observer.timeout = 60;
[observer addObserver];

NSLog(@"Executing testLoadAttendees...");
[_eventService loadAttendees:@"2269"];

[observer waitForNotification];

which I wrapped in a utility method:

- (void)executeAndTimeoutAfterSeconds:(int) timeoutSeconds usingBlock:(void(^)())block
{
    RKTestNotificationObserver *observer =
        [RKTestNotificationObserver notificationObserverForName:RKObjectRequestOperationDidFinishNotification object:nil];
    [observer addObserver];
    observer.timeout = timeoutSeconds;
    block();
    [observer waitForNotification];
}

so test are now executed using:

[self executeAndTimeoutAfterSeconds:60 usingBlock:^ {
    NSLog(@"Executing testLoadAttendees...");
    [_eventService loadAttendees:@"2269"];
}];