Implement Unit Testing for Core Data in iOS

Tifo Audi Alif Putra
The Startup
Published in
5 min readDec 28, 2020

Testing core data? is it possible? let me tell you how to do it.

Photo by AltumCode on Unsplash

Testing is a crucial part in software development process. Testing has some useful benefits like get feedback quickly when developing app in the early phase, useful for other teammates when work together because it can act as a documentation, and save more time in the future for reducing production issue probabilities. In this article, I will try to introduce you how to implement unit testing for iOS project that using Core Data as a data persistence.

Configure Core Data Stack for Testing

Unit test must following FIRST principle:

  • F : Fast
    Fast mean when you running a test, it should not take too much time. Your test should not be a blocker to another task. For example if we want to test a network layer or network service, it should just test the behaviour of it and not doing the real network request to the web service. We can do that by using mock service function to act like the real one and create mock response for simulate real response like get response from web service.
  • I : Isolated
    Isolated mean when you running a test, it should not change state outside the test it self.
  • R : Repeatable
    Repeatable mean when you running a test by a hundred times or more, the result must be same and not changed.
  • S : Self-verifying
    Self-verifying mean when you running a test, you are not allowed to print or log the result. Otherwise, your test should verified itself for getting the test result.
  • T : Timely
    Timely mean better to write the test first so you have initial knowledge about your app functionality. It take more efficient time for building the whole system.

Think what happen if we implement unit testing and using default core data stack. By default, the common way to implement core data is using NSSQLiteStoreType as a default store in NSPersistentStore. By doing that, if there are any changes from NSManagedObject and the changes are saved by NSManagedObjectContext then it will persist the changes and saved in SQLite database.

It have a probability when we running a test then it would change other content or the state outside the test itself. Then it break Isolated principle. The other problem could happen is when the test is finished, it could having a changes in SQLite database by deleting or creating something new, in a testing context it is not Fast.

The conclusion is if we implement unit testing and using default core data stack that using NSSQLiteStoreType as a default store, it break Fast and Isolated principles. Fortunately, we can change the store type in NSPersistentStore. There are four type of stores that we can use:

  • NSSQLiteStoreType
    By default NSPersistentStore use it as a default store type and implement SQLite as a database.
  • NSXMLStoreType
    Backed by XML file.
  • NSBinaryStoreType
    Backed by binary data file.
  • NSInMemoryStoreType
    This store type is save the data in memory. We will use this store type since it not persist the data if we have a changes if the app will terminated or closed.

So I will create new core data stack just for testing objective and I will show you how to choose NSInMemoryStoreType as our store in NSPersistentStore.

  1. We create NSPersistentStoreDescription instance, it have a description about the store used to load and create the persistent store later. Then we just need set it type to NSInMemoryStoreType.
  2. Then we have to create another persistent container object for testing requirement, and we set the container’s persistentStoreDescriptions property with store description object that we already created before.
  3. Finally, we assign the testPersistentContainer object to persistentContainer that inherited from CoreDataStack class.

From here, we already have new core data stack specifically for unit testing requirement. The next step is let’s setup the test class and create our first test.

  1. We setup system under test (sut) object with RestoDataStore type and also include the TestCoreDataStack instance. We will use sut for doing some kind of CRUD to the Menu object model.
  2. This just some kind of test lifecycle, we will initialise RestoDataStore and TestCoreDataStack in setupWithError function and pass them into sut and coreDataStack variable that we declared before. After the tests finished, then the system will call tearDownWithError function and we set the value of sut and coreDataStack to nil.
  3. Then, we will create our first test function to test Menu model. First we call addMenu function from sut and pass the return value into newMenu. From here, we want to test that newMenu is not nil and the value of name, desc, and price already suitable and the test is succeed.

Test Asynchronous Functionality

In default implementation, sometime we just need default viewContext from our core data stack for manipulate NSManagedObject and save the changes.

How about asynchronous functionality? doing like save bunch of data, or perform batch changes could block main thread. However, core data provide another context which run on background. We can perform some task like create menu, update menu, or delete menu by using background context.

But how to test asynchronous functionality?

Ok, here is how to do it:

  1. First, create new variable called backgroundContext and it will save the new background context given from our core data stack. We need to reinitialise the sut value and pass the backgroundContext as an argument to RestoDataStore initializer parameter. Then, because we want to test async function so we create expectation that took two arguments. The first argument is notification type, in other word we want to test that NSManagedObjectContextDidSave notification is received. The second argument is the backgroundContext itself.
  2. We ask backgroundContext to perform operation for create new menu and save it. In this step, we also need to make sure that the newMenu object is not nil otherwise the test is failed.
  3. Finally we call waitForExpectations with the timeout of 2 second. If the backgroundContext execute the operation more than 2 second then the test is failed. Then the final test is make sure that error object is also nil. If everything goes well, then our test is succeed.

Where to go from here

You can look and clone to the project here:

I also have already made some test for verify fetch menu, update menu, and delete the menu. From here, I encourage to try by yourself and implement it on your own project. This article is not perfect, and it will be good if you provide me some feedbacks (also with clap, hahaha :D ). Thank you so much and see you in another article.

--

--