Unit Testing CoreData Tip: The Singleton

It’s been a while since I was deep into CoreData, but here I am again. And this time, I’m writing proper unit tests.

Things were going pretty well – I had about 50 test cases with a few related asserts in each – until I started messing with saving, and fetching tests. I ran into some odd behavior where sometimes a particular test would pass, other times fail with different results in one assertion. All I was doing was creating a managed object, setting a relationship, saving, and reading back to check for proper order. Pretty trivial stuff.

I looked deeper.

If I ran just that one test case by itself, it would always pass. If I ran “All Tests”, it would always fail. Sometimes I would get several fetched objects when only one was expected. Sometimes those would be in different orders.

As I dug deeper, I attempted to reset my context on each test case

[self.managedObjectContext reset];

No luck.

To this point, my managedObjectContext has been a singleton – to follow the pattern of the rest of the appellation. I had a hunch the problem might be there – especially if multiple tests are running (concurrently, even?).

+(instancetype)inMemoryManager {
    static DataManager *sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[DataManager alloc] init];

        NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[sharedInstance managedObjectModel]];

        NSError *error;
        if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        }

        sharedInstance.persistentStoreCoordinator = persistentStoreCoordinator;
    });

    return sharedInstance;
}

I tried creating a separate for-testing-only manager that creates a new stack each time so I could hopefully avoid these apparent concurrency issues.

+(instancetype)inMemoryManagerForTesting {
    DataManager *dataManager = [[DataManager alloc] init];
    NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[dataManager managedObjectModel]];

    NSError *error;
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }
    dataManager.persistentStoreCoordinator = persistentStoreCoordinator;

    return dataManager;
}

That seems to do the trick, though I would have preferred sticking with the singleton. Don’t use CoreData stack singletons. Turns out @orta suggests the same [tweet] [blog post].

Leave a comment

Hey there! Come check out all-new content at my new mistercameron.com!