NSInMemoryStoreType is not a good substitute in unit tests

August 29, 2010    

So I was writing some tests for my KidChart app, which uses core data, and I wasted a ton of time, so I thought I’d post to warn people.  I wanted to avoid having to reset the state for each test, and I wanted the tests to run quickly, so I used NSInMemoryStoreType for my persistent store in my unit tests. This is a technique I’ve used before in other programming languages, and I was new to Core Data, so I was applying what I had done before to something I had insufficiently researched.

The app has a pretty simple data model.  There are Events, and each Event has a Child and a Behavior, and each Behavior has a good/bad score attached to it.  On my home screen, there’s a summary view, where I show the number of events for each child for each (good/bad) score for that day. I was thinking I could use an NSPredicate to grab just what I wanted.  Basically, what I wanted was:

1SELECT Child.Name , Behavior.Score ,count(Event. *)

2FROM Child , Behavior , Event

3WHERE Child.event.id = Event.id AND 4Event.Behavior_id = Behavior.id AND 5Event. date

‘Today at Midnight’ 6GROUP BY Child.Name , Behavior.Score

Except you can’t have direct SQL access.  So what I ended up with (after much iteration) was:

1NSFetchRequest *request = [[NSFetchRequest alloc] init]; 2NSEntityDescription *entity = [NSEntityDescription entityForName:@ “Child”

3
inManagedObjectContext:managedObjectContext ]; 4[request setEntity:entity ]; 5 6// Specify that the request should return dictionaries. 7[request setResultType:NSDictionaryResultType]; 8 9// Create an expression for the key path. 10NSExpression *childEventKeyPathExpression = [NSExpression expressionForKeyPath: 11 @ “events”]; 12NSExpression *onlyHappyEvents = [NSExpression expressionForSubquery: 13 childEventKeyPathExpression

14
usingIteratorVariable:@ “e”

15
predicate:[NSPredicate predicateWithFormat:@ “$e. 16behavior.score == 1 and $e.timeStamp > %@”

17
argumentArray:[NSArray arrayWithObject: currentDate ]]]; 18 19// Create an expression to represent the count value at the key path ’events' 20NSExpression *happyCountExpression = [NSExpression expressionForFunction:@ “count:”

21
arguments:[NSArray arrayWithObject: 22 onlyHappyEvents ]]; 23 24 25// Create an expression description using the minExpression and returning a date. 26NSExpressionDescription *happyCountExpressionDescription = [[NSExpressionDescription

27
alloc] init]; 28 29// The name is the key that will be used in the dictionary for the return value. 30[happyCountExpressionDescription setName:@ “happyEventCount”]; 31[happyCountExpressionDescription setExpression:happyCountExpression ]; 32[happyCountExpressionDescription setExpressionResultType:NSInteger16AttributeType]; 33 34NSExpression *onlySadEvents = [NSExpression expressionForSubquery: 35 childEventKeyPathExpression

36
usingIteratorVariable:@ “e”

37
predicate:[NSPredicate predicateWithFormat:@ “$e. 38behavior.score == -1 and $e.timeStamp > %@”

39
argumentArray:[NSArray arrayWithObject: currentDate ]]]; 40// Create an expression to represent the count value at the key path ’events' 41NSExpression *sadCountExpression = [NSExpression expressionForFunction:@ “count:”

42
arguments:[NSArray arrayWithObject:onlySadEvents ]] 43
; 44 45// Create an expression description using the minExpression and returning a date. 46NSExpressionDescription *sadCountExpressionDescription = [[NSExpressionDescription

47
alloc] init]; 48 49// The name is the key that will be used in the dictionary for the return value. 50[sadCountExpressionDescription setName:@ “sadEventCount”]; 51[sadCountExpressionDescription setExpression:sadCountExpression ]; 52[sadCountExpressionDescription setExpressionResultType:NSInteger16AttributeType];

53 54NSExpression *childNameKeyPathExpression = [NSExpression expressionForKeyPath: 55 @ “Name”]; 56NSExpressionDescription *nameExpressionDescription = [[NSExpressionDescription alloc] 57
init]; 58[nameExpressionDescription setName:@ “childName”]; 59[nameExpressionDescription setExpression:childNameKeyPathExpression ]; 60[nameExpressionDescription setExpressionResultType:NSStringAttributeType]; 61 62// Set the request’s properties to fetch just the property represented by the 63expressions. 64[request setPropertiesToFetch:[NSArray arrayWithObjects:nameExpressionDescription ,

65happyCountExpressionDescription , sadCountExpressionDescription , nil]]; 66

67// Execute the fetch. 68NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error ]; 69STAssertNil(error , @ “error not nil:%@",error );

Which is way more verbose than the corresponding SQL, but looks scarier than it actually is.  And it worked great.  And then, I changed my persistent store type to  NSSQLiteStoreType.  And it started throwing " NSInvalidArgumentException: -predicate only defined for abstract class” errors. And I got to start all over.