Reputation: 9887
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:
storeType
, in the app delegate class.-persistentStoreCoordinator
sets it to NSSQLiteStoreType
if it's nil
. This will be the default value and, in production, the only value, ensuring that things work properly when running the app.DEBUG
macro is set for all debug builds (including for my App Tests target)If DEBUG
is set, define a method in the app delegate, -resetCoreData
. The method looks like this:
#ifdef DEBUG
- (void)resetCoreData {
// Testing, we want to use the in memory store.
storeType = NSInMemoryStoreType;
// Disconnect core data.
__persistentStoreCoordinator = nil;
__managedObjectContext = nil;
// Set up defaults.
[self configureCoreDataDefaults];
}
#endif
Note that it sets the static variable storeType
to NSInMemoryStoreType
. The -configureCoreDataDefaults
method creates some managed objects that should always be present.
In my app test base class, I have -setup
call -resetCoreData
:
- (void)setUp {
[super setUp];
[[[UIApplication sharedApplication] delegate] resetCoreData];
}
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
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
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