I use Core Data to manage the schedule of objects, mainly for dependency injection (a subset of NSManagedObjects needs to be preserved, but this is not the subject of my question). When doing unit tests, I want to take on the creation of NSManagedObjects, replacing them with mocks.
At the moment, I have a candidate to use this to use the runtime method_exchangeImplementations to exchange [NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] with my own implementation (i.e. returning mocks). This works for the little test I did.
I have two questions regarding this:
- Is there a better way to replace creating Data Data Data objects than swizzling insertNewObjectForEntityForName: inManagedObjectContext? I did not go far at runtime or Core Data, and something obvious may be missing.
- My concept of creating a replacement object is to return mocking NSManagedObjects. I use OCMock, which will not directly mock NSManagedObject subclasses due to their dynamic
@property s. So far, my NSManagedObject clients are talking to protocols, not specific objects, so I am returning error protocols, not specific objects. Is there a better way?
Here is some kind of pseudo code to illustrate what I get. Here is a class that I can test:
@interface ClassUnderTest : NSObject - (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject; @end @interface ClassUnderTest() @property (strong, nonatomic, readonly) Thingy *myThingy; @property (strong, nonatomic, readonly) Thingo *myThingo; @end @implementation ClassUnderTest @synthesize myThingy = _myThingy, myThingo = _myThingo; - (id) initWithAnObject:(Thingy *)anObject anotherObject:(Thingo *)anotherObject { if((self = [super init])) { _myThingy = anObject; _myThingo = anotherObject; } return self; } @end
I decided to subclass Thingy and Thingo NSManagedObject, perhaps for saving, etc., but I can also replace init with something like:
@interface ClassUnderTest : NSObject - (id) initWithManageObjectContext:(NSManagedObjectContext *)context; @end @implementation ClassUnderTest @synthesize myThingy = managedObjectContext= _managedObjectContext, _myThingy, myThingo = _myThingo; - (id) initWithManageObjectContext:(NSManagedObjectContext *)context { if((self = [super init])) { _managedObjectContext = context; _myThingy = [NSEntityDescription insertNewObjectForEntityForName:@"Thingy" inManagedObjectContext:context]; _myThingo = [NSEntityDescription insertNewObjectForEntityForName:@"Thingo" inManagedObjectContext:context]; } return self; } @end
Then in my unit tests I can do something like:
- (void)setUp { Class entityDescrClass = [NSEntityDescription class]; Method originalMethod = class_getClassMethod(entityDescrClass, @selector(insertNewObjectForEntityForName:inManagedObjectContext:)); Method newMethod = class_getClassMethod([FakeEntityDescription class], @selector(insertNewObjectForEntityForName:inManagedObjectContext:)); method_exchangeImplementations(originalMethod, newMethod); }
... where my []FakeEntityDescription insertNewObjectForEntityForName:inManagedObjectContext] returns mocks instead of real NSManagedObjects (or the protocols they implement). The sole purpose of these layouts is to test the calls made by him during unit testing of ClassUnderTest. All return values will be truncated (including any getters related to other NSManagedObjects).
My test instances of ClassUnderTest will be created as part of unit tests, this way:
ClassUnderTest *testObject = [ClassUnderTest initWithManagedObjectContext:mockContext];
(the context will not actually be used in the test due to my swizzled insertNewObjectForEntityForName:inManagedObjectContext )
The point of all this? In any case, I will use Core Data for many classes, so I could use it to reduce the burden of managing changes in the constructors (each change in the constructor includes editing all clients, including a bunch of unit tests). If I had not used Core Data, I could consider something like Objection .