> ## Content Index
> Fetch the complete content index at: https://williamboles.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Can Unit Testing and Core Data become BFFs?
- URL: https://williamboles.com/can-unit-testing-and-core-data-become-bffs/
- Published: 2018-04-02T17:31:28.000Z
- Updated: 2026-04-01T08:59:12.000Z
- Description: Core Data and Unit Testing haven't always been the best of friends ⚔️. Core Data is all about persisting. Unit testing is all about forgetting. Here, we look at how they can work together with the help of UIKit to ensure that we gain the confidence we need to ship an app.
- Author: William Boles
- Tags: Core Data, Testing

Core Data and Unit Testing belong to the same friend group, but they've never really hit it off. Core Data is all about creating memories that persist long after the app has closed, whereas Unit Testing turns up, runs through its checklist, and then vanishes without a trace.

What on the surface looks like irredeemable differences needn't stop a beautiful friendship from blossoming - after all, `UIKit` won't stop inviting them to the same parties.

![Photo showing BFFs hugging](https://storage.ghost.io/c/b9/b4/b9b42214-5933-4584-8e56-9ee8ca21d23b/content/images/2018/03/BFFs.jpg)

This post will explore how to structure a Core Data stack so it get along with Unit Testing - without building a completely separate testing setup.

## Looking at Their Differences ⚔️

Core Data and Unit Testing have two fundamental disagreements:

**Treatment of data**

- Unit Testing treats data as ephemeral.
- The main use case of Core Data is persisting data between app executions.

**Tolerance for delays**

- Unit tests should be lightning quick to execute.
- Core Data typically has to communicate with a SQLite file on disk, which is slow (when compared with pure memory operations).

And like building any good relationship, all the changes will come from one side - Core Data.

> Please don't take that as genuine relationship advice 😜.

## Building That Friendship

[Core Data](https://developer.apple.com/documentation/coredata?ref=williamboles.com) can be configured in many different ways, which makes it difficult to talk about in the abstract. To keep things concrete, here's the specific stack we'll be building and testing in this post:

![Diagram showing what our Core Data stack will look like with parent and child contexts](https://storage.ghost.io/c/b9/b4/b9b42214-5933-4584-8e56-9ee8ca21d23b/content/images/2019/11/Core-Data-Stack-Parent-Child.jpg)

- [Persistent Container](https://developer.apple.com/documentation/coredata/nspersistentcontainer?ref=williamboles.com) is a fairly new member of the Core Data family. It was introduced in iOS 10 to help simplify the creation of the managed object model, persistent store coordinator and any managed object contexts.
- [Managed Object Context](https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext?ref=williamboles.com) is a temporary, in-memory record of all [NSManagedObject](https://developer.apple.com/documentation/coredata/nsmanagedobject?ref=williamboles.com) instances accessed, created or updated during its lifecycle. An app typically has multiple contexts in existence at any given time. These contexts will form a parent-child relationship. When a child context is saved, it pushes its changes to its parent's context, which then merges these changes into its own state. At the top of this parent-child hierarchy is the `main` context; this context, upon being saved, will push its changes into the persistent store.
- [Persistent Store Coordinator](https://developer.apple.com/documentation/coredata/nspersistentstorecoordinator?ref=williamboles.com) acts as an aggregator between the various contexts to ensure the integrity of the persistent store(s). It does this by serialising read/write operations from the contexts to its store(s). There is only one coordinator per Core Data stack.
- [Managed Object Model](https://developer.apple.com/library/content/documentation/DataManagement/Devpedia-CoreData/managedObjectModel.html?ref=williamboles.com###//apple%5Fref/doc/uid/TP40010398-CH30-SW1) is a set of entities that define each `NSManagedObject` subclass. An entity can be thought of as a table in a database.
- [Persistent Object Store](https://developer.apple.com/library/content/documentation/DataManagement/Devpedia-CoreData/persistentObjectStore.html?ref=williamboles.com) is an abstraction over the actual storage of our data. It handles communication with that storage, e.g. with SQLite storage, converts fetch requests into SQL statements.
- [Storage](https://developer.apple.com/library/content/documentation/DataManagement/Devpedia-CoreData/persistentStore.html?ref=williamboles.com###//apple%5Fref/doc/uid/TP40010398-CH29-SW1) It's common for Core Data storage to be a SQLite store, but the store could also be XML, binary or in-memory.

> While this post will gradually build up to a working example, if you are in a hurry, head on over to the [completed example](https://github.com/wibosco/TestingWithCoreData-Example?ref=williamboles.com) and take a look at `CoreDataManager`, `CoreDataManagerTests`, `ColorManager` and `ColorManagerTests` to see how things end up.

Let's build the above stack and unit test it, starting with the manager that will handle setting up and managing the Core Data stack:

```
// 1
class CoreDataManager {

    // 2
    let persistentContainer: NSPersistentContainer
    let backgroundContext: NSManagedObjectContext
    let mainContext: NSManagedObjectContext

    // MARK: - Init

    // 3
    private init(persistentContainer: NSPersistentContainer) {
        self.persistentContainer = persistentContainer

        // 4
        let backgroundContext = persistentContainer.newBackgroundContext()
        backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        self.backgroundContext = backgroundContext

        // 5
        let mainContext = persistentContainer.viewContext
        mainContext.automaticallyMergesChangesFromParent = true
        self.mainContext = mainContext
    }

    // MARK: - SetUp

    // 6
    static func setUp(completion: @escaping (Result<CoreDataManager, Error>) -> Void) {
        let container = NSPersistentContainer(name: "TestingWithCoreData_Example")

        container.loadPersistentStores { _, error in
            if let error = error {
                completion(.failure(error))
                return
            }

            completion(.success(CoreDataManager(persistentContainer: container)))
        }
    }
}
```

Here's what we did above:

1. `CoreDataManager` is responsible for setting up and owning the Core Data stack.
2. All three properties are `let` constants - once a `CoreDataManager` exists, its stack is fully formed and immutable.
3. The initialiser is `private`, so the only way to create a `CoreDataManager` is through the `setUp(completion:)` factory method. This guarantees that no one can get hold of an instance of `CoreDataManager` before its persistent store has loaded.
4. `backgroundContext` is a private queue context for write operations - creating, updating and deleting managed objects off the main thread. Its merge policy is set to `NSMergeByPropertyObjectTrumpMergePolicy`, meaning in-memory changes win over store conflicts on a property-by-property basis.
5. `mainContext` is the container's `viewContext`, tied to the main queue for feeding data to the UI. Setting `automaticallyMergesChangesFromParent` to `true` means it picks up changes saved by the background context without manual merging.
6. `setUp(completion:)` is a static factory that creates and loads the persistent container, then hands back a fully configured `CoreDataManager` via a `Result`. If loading fails, the error is passed back to the caller rather than crashing - the app layer gets to decide how to handle it. It's an asynchronous method because `loadPersistentStores(completionHandler:)` is asynchronous.

> It's unusual to have a `private` init'er, but doing it this way avoids having to have `precondition` or `fatalError` checks to ensure the properties of `CoreDataManager` are only accessed after `setUp(completion:)` is called.

Before we can write unit tests for `CoreDataManager`, we need to first write a helper for `NSPersistentContainer` that will allow us to more easily tear down the persistent store after each test to ensure that one test isn't able to contaminate any other test by leaving behind data:

```
extension NSPersistentContainer {
    func destroyPersistentStore() throws {
        guard let storeURL = persistentStoreDescriptions.first?.url,
              let storeType = persistentStoreDescriptions.first?.type else {
            return
        }

        try persistentStoreCoordinator.destroyPersistentStore(at: storeURL,
                                                              ofType: storeType,
                                                              options: nil)
    }
}
```

With the ability for each test to tidy up after itself, let's write some unit tests for `CoreDataManager`:

```
class CoreDataManagerTests: XCTestCase {

    // 1
    func test_givenNoSetup_whenSetUpFinishes_thenCoreDataManagerIsCreated() {
        // 2
        let setUpExpectation = expectation(description: "SetUp completion called")

        // 3
        CoreDataManager.setUp { result in
            // 4
            defer { setUpExpectation.fulfill() }

            // 5
            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            // 6
            try? sut.persistentContainer.destroyPersistentStore()
        }

        // 7
        waitForExpectations(timeout: 1.0)
    }
}
```

1. The test name follows the [Given-When-Then](https://martinfowler.com/bliki/GivenWhenThen.html?ref=williamboles.com) unit testing naming convention. This test checks that an instance of `CoreDataManager` is created when `setUp(completion:)` finishes.
2. As `setUp(completion:)` is asynchronous, we create an `XCTestExpectation` instance using the convenience method on `XCTestCase`, which will allow us to *wait* for the closure callback before determining if the test passed or failed. Think of an expectation as a promise that something will happen within a given time frame.
3. We call `CoreDataManager.setUp(completion:)` and fulfil the expectation inside the completion closure.
4. `defer` ensures the expectation is fulfilled regardless of which path through the closure we take - success or failure. This avoids duplicating the `fulfill` call.
5. We unwrap the `Result` and fail the test with a descriptive message if setup didn't succeed.
6. Because `CoreDataManager` uses an on-disk store, we clean up the SQLite file after we are done.
7. `waitForExpectations` pauses the test for up to one second, giving the store time to load. If the expectation isn't fulfilled, the test fails.

> There's no overridden `setUp` method creating a shared instance here. Due to the unusual way `CoreDataManager` is initialised, each test creates its own `sut` instance.

There's a problem with `test_givenNoSetup_whenSetUpFinishes_thenCoreDataManagerIsCreated` as it stands - it writes to an on-disk SQLite store. That's the production behaviour we want, but for tests, it means slower I/O and a SQLite file we need to clean up after every run. Core Data offers a better option for testing: [NSInMemoryStoreType](https://developer.apple.com/documentation/coredata/nsinmemorystoretype?ref=williamboles.com). An in-memory store keeps the object graph in RAM - no disk I/O, no leftover files, and the store vanishes the moment the test finishes.

> The default Store Type for Core Data is: [NSSQLiteStoreType](https://developer.apple.com/documentation/coredata/nssqlitestoretype?ref=williamboles.com).

Let's update `CoreDataManager` so that we can use `NSSQLiteStoreType` in production and `NSInMemoryStoreType` in unit testing:

```
class CoreDataManager {
    // Omitted unchanged functionality

    // 1
    static func setUp(storeType: String = NSSQLiteStoreType,
                  completion: @escaping (Result<CoreDataManager, Error>) -> Void) {
        let container = NSPersistentContainer(name: "TestingWithCoreData_Example")

        // 2
        let description = container.persistentStoreDescriptions.first
        description?.type = storeType

        container.loadPersistentStores { _, error in
            if let error = error {
                completion(.failure(error))
                return
            }

            completion(.success(CoreDataManager(persistentContainer: container)))
        }
    }
}
```

1. `setUp` now takes a `storeType` parameter, defaulting to `NSSQLiteStoreType`. This keeps the production call site clean while letting tests pass `NSInMemoryStoreType` for speed.
2. We override the container's default store description with whatever `storeType` was passed in. This is what lets us swap between on-disk and in-memory stores.

> As a long-term Objective-C iOS developer, I still get a thrill about being able to provide a default value to a parameter (like we do with the `storeType` parameter above) without having to create a whole new method.

With this change to `CoreDataManager`, we need to update `test_givenNoSetup_whenSetUpFinishes_thenCoreDataManagerIsCreated` to take advantage of it:

```
class CoreDataManagerTests: XCTestCase {

    func test_givenNoSetup_whenSetUpFinishes_thenCoreDataManagerIsCreated() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        // 1
        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            // 2
            guard let _ = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }
        }

        waitForExpectations(timeout: 1.0)
    }
}
```

1. We now pass in `NSInMemoryStoreType` as the store type - avoiding disk I/O and the need to clean up SQLite files afterwards.
2. The `guard` now just checks that setup succeeded - we don't need to hold onto the instance as we don't need it for clean up.

> Don't worry, the code we wrote for `destroyPersistentStore()` isn't going to be thrown away.

Let's add two more unit tests to ensure that the `persistentContainer` is configured with the expected store type:

```
class CoreDataManagerTests: XCTestCase {
    // Omitted unchanged code

    func test_givenNoSetup_whenSetUpFinishes_thenPersistentContainerLoadedOnDisk() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        // 1
        CoreDataManager.setUp { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            // 2
            XCTAssertEqual(sut.persistentContainer.persistentStoreDescriptions.first?.type, NSSQLiteStoreType)

            // 3
            try? sut.persistentContainer.destroyPersistentStore()
        }

        waitForExpectations(timeout: 1.0)
    }

    func test_givenNoSetup_whenSetUpFinishes_thenPersistentContainerLoadedInMemory() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        // 4
        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            // 5
            XCTAssertEqual(sut.persistentContainer.persistentStoreDescriptions.first?.type, NSInMemoryStoreType)
        }

        waitForExpectations(timeout: 1.0)
    }
}
```

1. We call `setUp(storeType:completion:)` without specifying a `storeType`, so it defaults to `NSSQLiteStoreType` \- the production path.
2. We assert that the persistent store description's type is `NSSQLiteStoreType`, confirming the default was applied.
3. Because this test created an on-disk SQLite file, we clean it up afterwards. The in-memory test below doesn't need this.
4. We pass `NSInMemoryStoreType` explicitly, verifying the alternative path that our tests will use.
5. We assert that the store description's type is `NSInMemoryStoreType`, confirming the parameter was applied.

> When running the tests, I noticed that `test_givenNoSetup_whenSetUpFinishes_thenPersistentContainerLoadedOnDisk` took on average *17* times longer to run than `test_givenNoSetup_whenSetUpFinishes_thenPersistentContainerLoadedInMemory` \- `0.001` vs `0.017` seconds.

With the above test passing, we can write the unit tests to ensure that the context properties of `CoreDataManager` are correctly configured:

```
class CoreDataManagerTests: XCTestCase {
    // Omitted unchanged functionality

    // 1
    func test_givenASetUpStack_whenBackgroundContextIsAccessed_thenItIsAPrivateQueue() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            XCTAssertEqual(sut.backgroundContext.concurrencyType, .privateQueueConcurrencyType)
        }

        waitForExpectations(timeout: 1.0)
    }

    // 2
    func test_givenASetUpStack_whenMainContextIsAccessed_thenItIsTheMainQueue() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            XCTAssertEqual(sut.mainContext.concurrencyType, .mainQueueConcurrencyType)
        }

        waitForExpectations(timeout: 1.0)
    }

    // 3
    func test_givenASetUpStack_whenBackgroundContextIsAccessed_thenMergePolicyIsPropertyObjectTrump() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            XCTAssertTrue(sut.backgroundContext.mergePolicy is NSMergePolicy)
            XCTAssertEqual((sut.backgroundContext.mergePolicy as? NSMergePolicy)?.mergeType, .mergeByPropertyObjectTrumpMergePolicyType)
        }

        waitForExpectations(timeout: 1.0)
    }

    // 4
    func test_givenASetUpStack_whenMainContextIsAccessed_thenItAutomaticallyMergesChangesFromParent() {
        let setUpExpectation = expectation(description: "SetUp completion called")

        CoreDataManager.setUp(storeType: NSInMemoryStoreType) { result in
            defer { setUpExpectation.fulfill() }

            guard let sut = try? result.get() else {
                XCTFail("Failed to set up Core Data stack")
                return
            }

            XCTAssertTrue(sut.mainContext.automaticallyMergesChangesFromParent)
        }

        waitForExpectations(timeout: 1.0)
    }
}
```

1. We verify that `backgroundContext` is configured with `privateQueueConcurrencyType`. Write operations need to happen off the main thread - a main queue background context would block the UI during saves.
2. We verify that `mainContext` is configured with `mainQueueConcurrencyType`. Since this context feeds data to the UI, it needs to be on the main queue so that updates to views happen safely.
3. We verify that `backgroundContext` has its merge policy set to `NSMergeByPropertyObjectTrumpMergePolicy`. This means in-memory changes win over store conflicts on a property-by-property basis. If someone accidentally removes or changes this line, the test catches it.
4. We verify that `mainContext` has `automaticallyMergesChangesFromParent` set to `true`. Without this, the UI wouldn't pick up changes saved by the background context without manual merging.

The good news is that we have finished creating and testing our Core Data stack 🎉.

## Putting That Friendship to Work

There is no point in creating a Core Data stack if we don't actually use it. In the [example project](https://github.com/wibosco/TestingWithCoreData-Example?ref=williamboles.com), we populate a collection view with instances of a subclass of `NSManagedObject` \- `Color`. To help us deal with these `Color` objects, we will be using a `ColorManager`:

```
class ColorManager {

    // 1
    let backgroundContext: NSManagedObjectContext

    // MARK: - Init

    init(backgroundContext: NSManagedObjectContext) {
        self.backgroundContext = backgroundContext
    }

    // MARK: - Create

    // 2
    func createColor(hex: String = UIColor.random.hexString,
                     date: Date = Date()) {
        backgroundContext.performAndWait {
            let color = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                            into: backgroundContext) as! Color
            color.hex = hex
            color.dateCreated = date

            try? backgroundContext.save()
        }
    }

    // MARK: - Deletion

    // 3
    func deleteColor(_ color: Color) {
        let objectID = color.objectID
        backgroundContext.performAndWait {
            if let colorInContext = try? backgroundContext.existingObject(with: objectID) {
                backgroundContext.delete(colorInContext)

                try? backgroundContext.save()
            }
        }
    }
}
```

1. `ColorManager` depends on an `NSManagedObjectContext` rather than on `CoreDataManager` directly. This decoupling is what makes the class testable - we can [inject](https://williamboles.com/let-dependency-injection-lead-you-to-a-better-design/) any context we like.
2. `createColor(hex:date:)` inserts a new `Color` into the background context, then saves. `performAndWait(_:)` ensures the work runs synchronously on the context's private queue. By injecting `hex` and `date` values, we can more easily unit test this method. Check out the [example project](https://github.com/wibosco/TestingWithCoreData-Example?ref=williamboles.com) to see the implementation of `UIColor.random`.
3. `deleteColor(_:)` grabs the `objectID` from the passed-in `Color` before entering the `performAndWait(_:)` block. This is important because the `Color` instance might belong to a different context - the main context, say - so we use `existingObject(with:)` to fetch it in the background context before deleting and saving.

> You may have noticed the `try?` calls when saving the context. In a production app, you'd want to handle those errors properly - logging them, surfacing them to the user, or propagating them to the caller. Here, silencing them keeps the example focused on the testing patterns rather than error handling.

> As responsible members of the Core Data community, our example app treats the `backgroundContext` as a read-write context and the `mainContext` as a read-only context. This ensures that any time-consuming tasks don't block the main (UI) thread.

Before implementing unit tests for `ColorManager`, we need to decide how to set up our testing Core Data stack. One approach is to build a separate, test-only stack - something like a `TestCoreDataStack` type with the async setup stripped out and the store type hardcoded to `NSInMemoryStoreType`. That gives us a cleaner separation between production and test code, but it also means maintaining a second stack configuration that has to mirror `CoreDataManager` exactly. Every time we change how `CoreDataManager` configures its contexts - merge policies, parent-child relationships, concurrency types, etc - we'd need to remember to update the test stack to match. And if we forget, our tests pass against a stack that doesn't reflect production.

The alternative is simpler: reuse `CoreDataManager` itself, passing `NSInMemoryStoreType` to its `setUp` method. This does mean a bug in `CoreDataManager` could cascade into other tests, resulting in false-failures, but that trade-off cuts the right way - if the stack is broken, I *want* those tests to fail too, because `ColorManager` can't function without a working stack.

While we are going with `CoreDataManager`, there is a practical problem with using it in our test suites. Its `setUp(storeType:completion:)` method is asynchronous, which means every test that needs a stack would have to create an expectation, wait for the callback, and only then start doing actual test work. That's a lot of ceremony for something that should be a single line in a test suite:

```
extension CoreDataManager {
    static func setUpForTesting() -> CoreDataManager {
        var manager: CoreDataManager!

        let semaphore = DispatchSemaphore(value: 0)

        setUp(storeType: NSInMemoryStoreType) { result in
            manager = try? result.get()
            semaphore.signal()
        }

        semaphore.wait()

        return manager
    }
}
```

`setUpForTesting()` solves this by wrapping the asynchronous call in a [DispatchSemaphore](https://developer.apple.com/documentation/dispatch/dispatchsemaphore?ref=williamboles.com). The semaphore starts at zero, so `wait()` blocks the calling thread immediately. When the in-memory store finishes loading, the completion assigns the manager and calls `signal()`, unblocking the wait. Since in-memory stores load near-instantly, the block is effectively momentary. The result is a synchronous factory that returns a fully configured `CoreDataManager` without expectations or callbacks.

> Using a `DispatchSemaphore` in this manner is dangerous, and this approach should be avoided in production code.

With our handy `setUpForTesting()` method, let's add some unit tests for `ColorManager`:

```
class ColorManagerTests: XCTestCase {
    // 1
    var sut: ColorManager!
    var coreDataManager: CoreDataManager!

    // MARK: - Lifecycle

    // 2
    override func setUp() {
        super.setUp()

        coreDataManager = CoreDataManager.setUpForTesting()

        sut = ColorManager(backgroundContext: coreDataManager.backgroundContext)
    }

    // MARK: - Tests

    // MARK: Init

    // 3
    func test_givenACoreDataStack_whenColorManagerIsInitialised_thenContextIsSetUp() {
        XCTAssertEqual(sut.backgroundContext, coreDataManager.backgroundContext)
    }
}
```

1. Alongside our `sut`, we declare a `CoreDataManager` instance. We need both because `sut` depends on the context that `coreDataManager` provides, and some tests will need to fetch data directly from the manager's contexts to verify what `ColorManager` did.
2. In `setUp()`, we use `setUpForTesting()` to create a fully configured in-memory Core Data stack synchronously, then inject its `backgroundContext` into `ColorManager`. A fresh stack is created before every test, so data from one test can never leak into another.
3. A simple sanity check that the context we injected is the same one assigned to the `backgroundContext` property.

Notice there's no `tearDown()` method here. Because `setUp()` creates a fresh in-memory stack before every test, there's nothing to clean up - the store vanishes when the test finishes.

> That `destroyPersistentStore()` helper we wrote earlier is only needed when the SQLite store is involved.

Let's look at a more complex test:

```
class ColorManagerTests: XCTestCase {
    // Omitted other functionality

    // 1
    func test_givenNoExistingColors_whenCreateColorIsCalled_thenAColorRecordIsCreated() {
        let hex = UIColor.random.hexString
        let date = Date.distantPast

        // 2
        sut.createColor(hex: hex,
                        date: date)

        // 3
        let request = NSFetchRequest<Color>(entityName: Color.className)
        let colors = try! coreDataManager.backgroundContext.fetch(request)

        // 4
        guard let color = colors.first else {
            XCTFail("color missing")
            return
        }

        // 5
        XCTAssertEqual(colors.count, 1)
        XCTAssertEqual(color.hex, hex)
        XCTAssertEqual(color.dateCreated, date)
    }
}
```

1. This test verifies that calling `createColor(hex:date:)` actually persists a fully populated `Color` record in Core Data.
2. We call `createColor(hex:date:)` on our `sut`. Since `performAndWait(_:)` is synchronous, the work is done by the time this returns.
3. We fetch all `Color` records directly from the background context, going to the store rather than relying on any state in `ColorManager`.
4. The `guard` gives us an early, descriptive failure if no `Color` was found. Without this, we'd get a less helpful index-out-of-bounds crash or a vague nil-related failure further down.
5. Three asserts, all verifying the same unit of work - that `createColor(hex:date:)` produced a single, fully populated `Color` and saved it. We check that the count is exactly one, that `hex` matches the `hex` value passed in, and that `dateCreated` matches the `date` value passed in.

We know that creating one `Color` is successful. Let's make sure that creating multiple `Color` records is also successful:

```
class ColorManagerTests: XCTestCase {
    // Omitted other functionality

    func test_givenNoExistingColors_whenCreateColorIsCalledMultipleTimes_thenMultipleColorRecordsAreCreated() {
        sut.createColor()
        sut.createColor()

        let request = NSFetchRequest<Color>(entityName: Color.className)
        let colors = try! coreDataManager.backgroundContext.fetch(request)

        XCTAssertEqual(colors.count, 2)
        XCTAssertNotEqual(colors[0].hex, colors[1].hex)
    }
}
```

Testing the deletion of a `Color` follows a similar pattern:

```
class ColorManagerTests: XCTestCase {
    // Omitted other functionality

    // 1
    func test_givenMultipleColors_whenDeleteColorIsCalled_thenColorRecordIsDeleted() {
        // 2
        let colorA = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorB = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorC = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color

        // 3
        sut.deleteColor(colorB)

        // 4
        let request = NSFetchRequest<Color>(entityName: Color.className)
        let backgroundContextColors = try! coreDataManager.backgroundContext.fetch(request)

        // 5
        XCTAssertEqual(backgroundContextColors.count, 2)
        XCTAssertTrue(backgroundContextColors.contains(colorA))
        XCTAssertTrue(backgroundContextColors.contains(colorC))
    }

    // 6
    func test_givenAnAlreadyDeletedColor_whenDeleteColorIsCalledAgainForSameColor_thenNoAdditionalRecordsAreDeleted() {
        let colorA = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorB = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorC = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color

        sut.deleteColor(colorB)
        sut.deleteColor(colorB)

        let request = NSFetchRequest<Color>(entityName: Color.className)
        let colors = try! coreDataManager.backgroundContext.fetch(request)

        XCTAssertEqual(colors.count, 2)
        XCTAssertTrue(colors.contains(colorA))
        XCTAssertTrue(colors.contains(colorC))
    }
}
```

1. This test verifies that calling `deleteColor(_:)` removes the correct record and leaves the others untouched.
2. We insert three `Color` records to give us a known starting state. Using three rather than one lets us verify that only the targeted record was removed - if we'd inserted just one, a passing test couldn't distinguish between "deleted the right record" and "deleted everything."
3. We delete the middle record, `colorB`.
4. We fetch all remaining `Color` records directly from the background context, going to the store rather than relying on any state in `ColorManager`.
5. We assert that exactly two records survive - specifically `colorA` and `colorC`. This confirms that `colorB` was deleted and nothing else was affected.
6. This test covers the edge case where `deleteColor(_:)` is called twice with the same `Color` \- plausible from a UI double-tap. We verify it handles this gracefully, leaving the other two records intact rather than crashing or deleting additional records.

There is one special case worth its own test. Remember that `deleteColor(_:)` grabs the `objectID` before entering the `performAndWait(_:)` block - that's because the `Color` instance passed in might belong to the main context, not the background context. The previous tests always deleted a `Color` from the same context it was created in, so that `objectID` bridge never had to do any real work. This test puts it under pressure:

```
class ColorManagerTests: XCTestCase {
    // Omitted other functionality

    func test_givenAColorInstanceFromTheMainContext_whenDeleteColorIsCalled_thenColorRecordIsDeleted() {
        // 1
        let colorA = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorB = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color
        let colorC = NSEntityDescription.insertNewObject(forEntityName: Color.className,
                                                         into: coreDataManager.backgroundContext) as! Color

        // 2
        let mainContextColor = coreDataManager.mainContext.object(with: colorB.objectID) as! Color

        // 3
        sut.deleteColor(mainContextColor)

        // 4
        let request = NSFetchRequest<Color>(entityName: Color.className)
        let backgroundContextColors = try! coreDataManager.backgroundContext.fetch(request)

        XCTAssertEqual(backgroundContextColors.count, 2)
        XCTAssertTrue(backgroundContextColors.contains(colorA))
        XCTAssertTrue(backgroundContextColors.contains(colorC))
    }
}
```

1. We insert three `Color` records into the background context as before.
2. This is the key difference from the previous deletion test. We retrieve `colorB` from the main context using its `objectID`. In our app, the UI reads from the main context, so when a user taps delete, the `Color` instance passed to `deleteColor(_:)` will belong to that context, not the background context.
3. We pass the main context's `Color` instance into `deleteColor(_:)`, which internally resolves it in the background context before deleting. This verifies that the cross-context handoff works correctly.
4. We fetch from the background context and confirm the same outcome - two records survive, and the right one was deleted.

That's the functionality in `ColorManager` fully covered by unit tests - well done on making it here!

## Best Friends Forever?

In the above code snippets, we have seen that unit testing with Core Data doesn't need to be that much more difficult than unit testing in general, with most of that added *difficulty* coming in the set-up of the Core Data stack. And while Core Data and Unit Testing may not become BFFs, we've seen that they can become firm friends 👫. That friendship is built on small alterations to our Core Data stack, which allows its data to be more easily discarded.

To see the complete working example, visit the [repository](https://github.com/wibosco/TestingWithCoreData-Example?ref=williamboles.com) and clone the project.