theory
theory

Reputation: 9887

How do I Switch to an in-memory store when running iOS app tests?

I have a pretty standard Xcode-generated interface for Core Data objects, namely these properties on my app delegate:

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

Now I'm writing application tests, but I want to use an in-memory database for core data that's reset every time a test runs. I've figured out a way to do it, but it feels totally hinky:

This gives me what I want: A fresh core data store with default objects created for every single test method.

But it's annoying. I've essentially added knowledge of the testing environment to my app delegate, to make it behave differently when running app tests. Gross!

So, what's a better way to do this? How do you do it?

Upvotes: 2

Views: 987

Answers (2)

theory
theory

Reputation: 9887

A followup to @eduardo-costa's answer, which I have accepted, with the code I used to make it work.

First, I created a DAO class and moved all of the core data properties there. The .h file looks like this:

@interface CollectionsDAO : NSObject

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

+ (CollectionsDAO *)defaultDAO;

@end

Now I just use this class wherever I need to access my core data stuff. -defaultDAO returns a static instance of the class, so that I can just use one everywhere. What you don't see is a private instance method, -storeType, which returns NSSQLiteStoreType. This is used to create the store. I'll come back to it below.

Next, I created a category on this class for use in tests. The header file:

#import "CollectionsDAO.h"

@interface CollectionsDAO (Test)

+ (void)setupTestDAO;
+ (void)clearData;

@end

And the implementation:

#import "CollectionsDAO+Test.h"
#include <objc/runtime.h>

static CollectionsDAO *testDAO;
@implementation CollectionsDAO (Testing)

+ (CollectionsDAO *)testDAO {
    if (testDAO == nil) testDAO = [[self alloc] init];
    return testDAO;
}

+ (void)setupTestDAO {
    method_setImplementation(
        class_getClassMethod(self.class, @selector(defaultDAO)),
        method_getImplementation(class_getClassMethod(self.class, @selector(testDAO)))
    );
}

+ (void)clearData {
    testDAO = nil;
}

- (NSString *)storeType {
    return NSInMemoryStoreType;
}

@end

Note that -storeType replaces the default, private method of the same name, returning NSInMemoryStoreType, so that the data will be stored in memory.

Now, in my my testing base class, I just use this category like so:

#import "CollectionsDAO+Test.h"

@implementation AppTestBase
+ (void)initialize {
    if (self == AppTestBase.class) {
        [CollectionsDAO setupTestDAO];
    }
}
- (void)tearDown {
    [CollectionsDAO clearData];
}
@end

And now the test always uses memory for storing core data data, the test instance of the DAO is always returned by +defaultDAO (because +setupTestDAO swizzles it into place), and the core data is cleared after every test.

I think that this is a lot cleaner than what I had before. It took me a while to get it figured out, but Eduardo's answer was correct, I just had to work at it for a while to figure out the details.

Upvotes: 4

Eduardo Costa
Eduardo Costa

Reputation: 1994

I recommend creating a DAO or similar to isolate the Core Data setup. Then, using a category, you can define and use this "resetCoreData" in your test target.

Upvotes: 0

Related Questions