Unit Testing Core Data in iOS
Testing code is a crucial part of app development, and Core Data is not exempt from this. This tutorial will teach you how you can test Core Data. By Graham Connolly.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Unit Testing Core Data in iOS
25 mins
- Getting Started
- What is Unit Testing?
- CoreData Stack for Testing
- Adding the TestCoreDataStack
- Different Stores
- Writing Your First Test
- The Set Up and Tear Down
- Adding a Report
- Testing Asynchronous Code
- Testing Save
- Test Driven Development (TDD)
- The Red, Green, Refactor Cycle
- Fetching Reports
- Updating a Report
- Extending Functionality
- Deleting a Report
- Where to Go From Here?
Testing your code is a crucial part of your app development journey. Although testing initially takes some time getting used to, it comes with a long list of benefits such as:
- Allowing you to make changes without having to worry that parts of your app will break.
- Speeding up debugging sessions.
- Forcing you to think about how to structure your code in a more organized manner.
And in this tutorial, you’ll learn how to apply the benefits of testing to your Core Data Models.
You’ll work with PandemicReport, a simple but excellent pandemic report tracker. You’ll focus on writing unit tests for the project’s Core Data Model, and along the way you’ll learn:
- What Unit Testing is and why it’s important.
- How to write a Core Data Stack suitable for testing.
- How to Unit Test your Core Data Models.
- About TDD Methodologies.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. Inside the starter project you’ll find PandemicTracker, an app that displays a list of infection reports.
You can add new reports and edit existing ones. The app persists the reports from session to session using Core Data. Build and run to check out the app.
The app shows a list of pandemic reports saved in Core Data. Currently, you have no reports. Add one by clicking the add button in the navigation bar.
Then, add a report entry by entering values into the text fields.
Next, tap Save to save your report to Core Data and dismiss this screen.
The list now contains your entry.
In Xcode, look at the main files you’ll work on:
- CoreDataStack.swift: An object wrapper for managing your app’s Core Data model layer.
- ReportService.swift: Manages the app’s business logic.
- ViewController.swift: Displays a list of reports saved in Core Data. Tapping + displays an entry form for a new report.
-
ReportDetailsTableViewController.swift: Shows the selected report’s details and lets you edit existing values. This also acts as an input form when you tap + in
ViewController
.
You’ll explore why writing unit tests for Core Data can be trickier than it sounds in the next few sections.
What is Unit Testing?
Unit Testing is the task of breaking down a project into smaller, testable pieces of code. For example, you could break down the logic of Messages on the iPhone into smaller units of functionality like this:
- Assigning a recipient, or recipients, to the message.
- Writing the text in the text area.
- Adding an emoji.
- Adding an image.
- Attaching a GIF.
- Attaching an Animoji.
While this may seem like a lot of extra work, there are many benefits of testing:
- A unit test verifies your code works as intended.
- Writing tests can catch bugs before they go into production.
- Tests also act as documentation for other developers.
- Unit tests save time when compared to manual testing.
- A failed test during development lets you know something is broken.
In iOS, unit tests run in the same environment as the app you’re testing. As a result, this can lead to problems if a running test modifies the app’s state.
CoreData Stack for Testing
The project’s Core Data stack currently uses a SQLite database as its storage. When running tests, you don’t want test or dummy data interfering with your app’s storage.
To write good unit tests, follow the acronym FIRST:
- Fast: Unit Tests run quickly.
- Isolated: They should function independently of other tests.
- Repeatable: A test should produce the same results every time it’s executed.
- Self-verifying: A test should either pass or fail. You shouldn’t need to check the console or a log file to determine if the test has succeeded.
- Timely: Write your tests first so they can act as a blueprint for the functionality you add.
Core Data writes and saves data to a database file on a simulator or device. Since one test may overwrite the contents of another test you can’t consider them Isolated.
Since the data saves to disk, the data in your database grows over time and the state of the environment may be different on each test run. As a result, those tests aren’t Repeatable.
After a test finishes, deleting and then recreating the contents of the database isn’t Fast.
You might think, “Well, I guess I can’t test Core Data because it’s not testable”. Think again.
The solution is to create a Core Data stack subclass that uses an in-memory store rather than the current SQLite store. Because an in-memory store isn’t persisted to disk, when the test finishes executing, the in-memory store releases its data.
You’ll create this subclass in the next section.
Adding the TestCoreDataStack
First up, create a subclass of CoreDataStack
under the PandemicReportTests group and name it TestCoreDataStack.swift.
Next, add the following to the file:
import CoreData
import PandemicReport
class TestCoreDataStack: CoreDataStack {
override init() {
super.init()
// 1
let persistentStoreDescription = NSPersistentStoreDescription()
persistentStoreDescription.type = NSInMemoryStoreType
// 2
let container = NSPersistentContainer(
name: CoreDataStack.modelName,
managedObjectModel: CoreDataStack.model)
// 3
container.persistentStoreDescriptions = [persistentStoreDescription]
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
// 4
storeContainer = container
}
}
Here, the code:
- Creates an in-memory persistent store.
- Creates an
NSPersistentContainer
instance, passing in themodelName
andNSManageObjectModel
stored in theCoreDataStack
. - Assigns the in-memory persistent store to the container.
- Overrides the
storeContainer
inCoreDataStack
.
Nice! With this class in place, you have the baseline for creating tests for you Core Data Model
Different Stores
Above you used an in-memory store, but you may wonder what other options you have available. There are four persistent stores available in Core Data:
- NSSQLiteStoreType: The most common store used for Core Data is backed by a SQLite database. Xcode’s Core Data Template uses this by default, and it’s also the store used in the project.
- NSXMLStoreType: Backed by an XML file.
- NSBinaryStoreType: Backed by a binary data file.
- NSInMemoryStoreType: This store type saves data to memory so it isn’t persisted. This is useful for unit testing because the data disappears if the app terminates.
With this in place, it’s time to write your first test.