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.

4.4 (18) · 1 Review

Download materials
Save for later
Share

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.
Note: This tutorial assumes you know the basics of Core Data. If you’re new to Core Data, check out the Getting Started with Core Data Tutorial first.

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.

Main Screen of App with Empty List

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.

Pandemic Report Entry Form

Then, add a report entry by entering values into the text fields.

Pandemic Report form with values

Next, tap Save to save your report to Core Data and dismiss this screen.

List Contains Your Entry

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.

Note: If you’re getting started with testing in iOS, or would like a refresher, check out our tutorial on iOS Unit Testing and UI Testing.

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.

Creating Adding the TestCoreDataStack

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:

  1. Creates an in-memory persistent store.
  2. Creates an NSPersistentContainer instance, passing in the modelName and NSManageObjectModel stored in the CoreDataStack.
  3. Assigns the in-memory persistent store to the container.
  4. Overrides the storeContainer in CoreDataStack.

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.

core data types

Note: If you’d like to learn more about the different stores in Core Data, check out the Apple Documentation on Persistent Store Types.

With this in place, it’s time to write your first test.