NSInMemoryStoreType is not a good substitute in unit tests
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.