I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
Unit Testing Model Layer with Core Data and Swift
Updated on November 19, 2015 – Swift 2.0
As I approached testing my Core Data model, I have to admit I was apprehensive. How in the world was I going to write unit tests for my model layer that depended on a database. Past experience with trying to write tests with databases was painful. I feared the same would be the case with Core Data.
To my surprise, unit testing my Core Data model layer has been… well… amazing. With little effort, I’ve been able to write the unit tests I’ve wanted. The process went something like this for me:
- Create an
NSManagedObject
subclass of the Core Data entity that I need in my unit test. (This just makes things easier from an Xcode code-completion standpoint) - Write a helper function to set up an in-memory
NSManagedObjectContext
. Avoiding the need to use an actual sqlite database is pretty handy. It allows for quick-running tests and easy iterations over the data model itself. - Write unit tests using the in-memory
NSManagedObjectContext
.
I’ve already written about creating an NSManagedObject
subclass, so I will unpack steps 2 and 3 in this blog entry.
Set up an in-memory NSManagedObjectContext
A Stack Overflow question+answer sparked some thoughts. The idea and the code both came from there. The answer uses Objective-C, so my contribution is that I’ve written it in Swift. In my project, I created a new Swift file called “CoreDataHelpers.swift” in my tests target. Here’s a look at the helper function:
1import CoreData
2
3func setUpInMemoryManagedObjectContext() -> NSManagedObjectContext {
4 let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!
5
6 let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
7
8 do {
9 try persistentStoreCoordinator.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, url: nil, options: nil)
10 } catch {
11 print("Adding in-memory persistent store failed")
12 }
13
14 let managedObjectContext = NSManagedObjectContext()
15 managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
16
17 return managedObjectContext
18}
Observations
I’ll be honest, I’m only starting to put together the pieces involved in setting up the Core Data stack. Working through these unit testing techniques has solidified a lot. Analyzing the helper method from the bottom up has made some sense out of how to configure everything:
- I need an
NSManagedObjectContext
whoseNSPersistentStoreCoordinator
property uses an in-memory store. - To get such an
NSManagedObjectContext
, I need to add a persistent store with a type ofNSInMemoryStoreType
to anNSPersistentStoreCoordinator
instance. (note the line that’s highlighted) - Of course, in order to do that, I need an
NSPersistentStoreCoordinator
instance, and I can’t get one of those unless I initialize it with anNSManagedObjectModel
. - To get an
NSManagedObjectModel
, I use the class method,mergedModelFromBundles()
to grab it from my main bundle. - Fast-forwarding now: With a proper
NSManagedObjectModel
instance, I can create anNSPersistentStoreCoordinator
instance with it and add anNSInMemoryStoreType
to thatpersistentStoreCoordinator
. Finally, I can initialize anNSManagedObjectContext
, assign the configuredpersistentStoreCoordinator
to the context, and return it.
Whew! This whole process felt a lot like reading If You Give a Mouse a Cookie, but that may be because I’ve read it a few hundred times to my 2 year old. :]
Writing the unit test(s)
With the ability to get an NSManagedObjectContext
instance that’s using an in-memory store, the unit tests using Entities from my Core Data model are quite easy.
Here’s a sample test:
1import CoreData
2
3class TestsUsingModelFromCoreData: XCTestCase {
4 func testSomethingUsingCoreData() {
5 let managedObjectContext = setUpInMemoryManagedObjectContext()
6 let entity = NSEntityDescription.insertNewObjectForEntityForName("EntityName", inManagedObjectContext: managedObjectContext)
7
8 // model setup
9
10 // XCTAssert
11 }
12}
Conclusion
I was so surprised at how straightforward the test was. The helper function makes a world of difference for me. I hope it does for you, too!