Core Data Unit Testing

How best to structure an app so that you can unit test with Core Data? Thats the question Ive tried to answer with the iOS app template available here.

Features
This working app template has the following features :
* In Memory Managed Persistent Store (and context) – just for the tests.
* An example of how this is used during a unit test
* ViewModel pattern, making UIViewControllers nice and slim.
* Dependency Injection – no use of a singleton, for eg. Better for testing.
* Helpers for fetching and deleting Core Data objects using generics.

In-Memory Persistent Store
The central feature of Core Data is the ability to store data in a persistent store that allows data to stick around permanently, ie after the app is no longer running. Importantly though, when unit testing, you don’t want to use that persistent data. You want to have a temporary persistent store separate to the real one that the app uses. For this, you need to use an In Memory Persistent Store, and you can see how those are created here, in the first class func.

Using this temporary store, along with the context created with it, allows you to create and delete objects, without affecting any real data that you have in the local installation of your app. Any data in the main apps persistent store would affect the outcome of your unit tests too, so this ensures that we have a clean, empty store to test with. And any creating or deleting of objects does not touch the real persistent store. You cant unit test Core Data without this (and if you are, it’s not going to work).

At the time of writing, there is a recurring bug in Core Data unit testing, where objects can appear unrecognised. To get around that, I instantiate and then delete an object before doing any testing, and the problem no longer occurs. I do this just after the point where I’ve created my in-memory persistent store.

An example unit test
There is an example unit test available here. This swift file is supposed to contain all unit tests for the MainViewModel class. As such, there is a class property to contain an instance of MainViewModel, and also a managed object context created with an In-Memory Persistent Store, as described above.

let viewModel = MainViewModel()
let context = UnitTestHelpers.setUpInMemoryManagedObjectContext()

During the unit test setup function, we pass this context into the MainViewModel to replace the one it would contain during normal running of the app. This is dependency injection in action : we can use all functions of the MainViewModel class, and the context used will be the one we set here, rather than the main context which would link to the apps main persistent store. In this way, we dont mess with that real persistent data during tests. This is explained further below.

override func setUp() {
    super.setUp()
    //put our test context onto the viewmodel
    viewModel.context = self.context
}

So now we can test one of our MainViewModel function : createAnObject. It should do what it says on the tin – add an example object into our Core Data context. Having called this function any number of times, we should be able to do a fetch and find the same number of objects in Core Data, as the amount of times we called it. So in our test, I call it three times, then i check that there are 3 objects fetched back from Core Data afterwards.

func testCreateAnObject() {
    //empty store of objects by deleting all.  then add 3 object to it.
    do {
        try UnitTestHelpers.deleteAllObjects(objectType: ExampleObject.self, withContext: context)
        try viewModel.createAnObject()
        try viewModel.createAnObject()
        try viewModel.createAnObject()
    } catch {
        if let vmerror = error as? ViewModelError {
            print("error : \(vmerror.localizedDescription)")
        }
        XCTFail("Could not create objects")
    }
    //create a fetch request to retrieve all those objects
    let fetchRequest = ExampleObject.fetchRequest() as NSFetchRequest
    do {
        let results = try context.fetch(fetchRequest)
        XCTAssert(results.count == 3)
    } catch {
        XCTFail("Unabled to fetch objects")
    }
}

The View-Model Pattern
Theres plenty of information out there now about the View-Model pattern, so I wont go into a full explanation of what it is here. In terms of its advantages for unit-testing, there are two main reasons, the first being purely simplicity. If our UIViewControllers are only responsible for displaying data, they don’t really require unit testing at all. We require a unit test to check that a function does a particular task, except for tasks related to putting things on the screen. (For that we can also create UIUnitTests – but Im not concerned with that in this app) Unit testing a UIViewController is a little more complicated than testing a simple class, perhaps involving going through the view lifecycle, or loading a nib or storyboard. If your data work is being done by an accompanying ViewModel, it means no need to test the UIViewController, and none of the accompanying difficulty that can come with it.

Second – UIViewControllers have a habit of getting out of hand in terms of size, if not using the ViewModel pattern. I have seen this time and again throughout my years developing apps. Having one class just doing so much stuff is never good, and goes against the Single Responsibility Principle. Seen in these terms, the single responsibility of the view controller should be to control the view, ie what it shows on screen, and nothing else. By doing the data work on our ViewModel, we can follow that principle better.

Dependency Injection
Ive often seen apps that use a Singleton class for managing Core Data. It can make sense in some situations: you often need to call to save context for example, and its a pain to have to get a reference to the AppDelegate each time, which is where a standard Core Data app has its context property.

However, its not convenient to use Singletons when unit testing. They can by nature persist outside the current realms of the test in progress, and so might contain old data or states that could affect the current test. Theres a good argument about why singletons are not suitable for unit tests in [this article](http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/).

So instead, we use dependency injection, and it’s just a much nicer way to do things. In the app template, our persistent store and managed object contexts are created in the AppDelegate. Then during appDidFinishLaunching , we get a reference to our MainViewController (and its accompanying ViewModel), and we set a pointer to that context. This means obviously, that throughout the MainViewModels operation, it can access the context easily by just using self.context. We can continue to propagate that context throughout the app by setting further pointers on subsequent UIViewControllers during segues.

guard let mainVC = self.window?.rootViewController as? MainViewController else {
    fatalError("Unable to get access to main window")
}
let context = self.persistentContainer.viewContext
mainVC.viewModel.context = context

And so to the big bonus for testing : when we create an instance of our MainViewModel within the unit test, we just set that pointer to our new context, linked to the In Memory Persistent Store. So very easily, all Core Data work throughout the class does not affect the normal store, and just works with our in-memory one. And not a singleton in sight.

override func setUp() {
    super.setUp()
    //put our test context onto the viewmodel
    viewModel.context = self.context
}

Generic fetching and sorting of Core Data objects

Finally, heres a handy method using generics that will fetch all objects of a given type, and sort them by a given property too. It’s the last class here. Typical usage is like this :

do {
    let objects : [ExampleObject] = try UnitTestHelpers.fetchObjects(withContext: context, sortedBy: "", ascending: true)
} catch {
    //handle failing that fetch
}

Replacing the ExampleObject with the object type from your own Core Data model is enough to tell the fetch function exactly what you are after. You can pass in nil if you don’t want the objects sorted, or pass in the name of the property you want to sort by as a String, along with true or false for ascending / descending.

Thats it : please let me know any feedback you might have on this approach to unit testing Core Data, and how it could be improved.

Leave a Reply

Your email address will not be published. Required fields are marked *