Unit testing with Swift and Core Data

The problem

Unit testing with Core Data can be complicated. We want to reduce any dependencies in order to isolate the class under test. But Core Data itself is a big dependency. Graham Lee wrote a couple blog posts[1],[2] several years ago on patterns for testing Core Data applications. He describes two patterns for isolating the the class under test. While Graham prefers to remove the Core Data dependency entirely, I’ve tended to use his second pattern by creating an in-memory Core Data stack that I build up and tear down with each test. I’ve done this for years using Objective-C; but now that Swift has come on the scene, I’ve had to adapt the process somewhat; and I’ll describe that here.

Application test example

We’ll start out by constructing a prototypical Employee/Manager model in a sample app. I’ve called the product (and module) name “Zoom”. This is important to know because we’ll need to prepend out NSManagedObject model class names in the model editor with the module name. Because of what I regard as an Xcode 6 bug, you should not prefix the class names in the model editor until after you’ve generated the class files assuming you want Xcode to do that step for you. If you namespace the class names beforehand, then the NSManagedObject subclass generation function in Xcode 6 won’t work properly.

Entities and NSManagedObject subclasses

With that in mind, create two entities: Boss and Employee. Give the Boss entity, attributes of firstName (String) and lastName (String). Give the Employee entity, attributes of firstName, lastName, salary (Integer 16), and dateHired (Date).

swift core data class nameConfigure the relationships between Boss and Employee. That should be obvious Boss to Employee is To-many and then the reciprocal to keep Core Data happy.

Remember to set the Class field in the Entity inspector; but leave off the namespace if you’re first generating the NSManagedObject subclasses. Your generated Employee class should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
import Foundation
import CoreData

class Employee: NSManagedObject {

@NSManaged var firstName: String
@NSManaged var lastName: String
@NSManaged var dateHired: NSDate
@NSManaged var salary: NSNumber
@NSManaged var boss: Boss

}

and the Boss class should look like this:

1
2
3
4
5
6
7
8
9
10
import Foundation
import CoreData

class Boss: NSManagedObject {

@NSManaged var lastName: String
@NSManaged var firstName: String
@NSManaged var employees: NSSet

}

When generating the subclasses for your model entities, remember to add the files to both the main target and the unit test target. The same goes for the model file itself. Now that we’ve set up the NSManagedObject subclasses for our Core Data entities, we can namespace the classes in the model editor:

swift core data class name

The Core Data stack

In my main application, I’ll just use the templated Core Data stack. It works as a starting point and it also can serve as the in-memory stack for our unit tests. To simplify testing of classes that use Core Data, I start with a base class that sets up and tears down our in-memory stack. We’ll call that class CoreDataTestCase.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

class CoreDataTestCase:XCTestCase {
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle().URLForResource("Zoom", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)
}()

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application’s saved data."
if coordinator!.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil, error: &error) == nil {
coordinator = nil
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application’s saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError.errorWithDomain("YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
NSLog("Unresolved error (error), (error!.userInfo)")
abort()
}

return coordinator
}()

lazy var managedObjectContext: NSManagedObjectContext? = {
let coordinator = self.persistentStoreCoordinator
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()

override func setUp() {

}

override func tearDown() {
managedObjectContext = nil
}
}

Example test class

We’ll derive our test classes from this base class. For example, to test our Boss class, we could begin with a test case class that derives from CoreDataTestCase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Foundation
import CoreData
import XCTest

class BossTestCase: CoreDataTestCase {
var boss:Boss?

override func setUp() {
super.setUp()
let entity = NSEntityDescription.entityForName("Boss", inManagedObjectContext: managedObjectContext!)
boss = Boss(entity: entity!, insertIntoManagedObjectContext: managedObjectContext)
}

func testThatWeCanCreateBoss() {
XCTAssertNotNil(self.boss, "unable to create a boss")
}
}

Conclusion

Creating an in-memory store doesn’t completely eliminate the Core Data dependency; but it reduces the pre-test assumptions considerably and makes for more controlled testing in Core Data applications.


  1. Lee, Graham. (6 September, 2009) Unit testing Core Data-driven apps. Retrieved Septeber 28, 2014, from http://iamleeg.blogspot.com/2009/09/unit-testing-core-data-driven-apps.html

  2. Lee, Graham. (10 January, 2010) Unit testing Core Data-drive apps, fit the second. Retrieved September 28, 2014, from http://iamleeg.blogspot.com/2010/01/unit-testing-core-data-driven-apps-fit.html