Implement Unit Testing for Core Data in iOS
Testing core data? is it possible? let me tell you how to do it.
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:
By default NSPersistentStore use it as a default store type and implement SQLite as a database.
Backed by XML file.
Backed by binary data file.
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.
- 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.
- Then we have to create another persistent container object for testing requirement, and we set the container’s
persistentStoreDescriptionsproperty with store description object that we already created before.
- Finally, we assign the
persistentContainerthat 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.
- 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.
- This just some kind of test lifecycle, we will initialise RestoDataStore and TestCoreDataStack in
setupWithErrorfunction and pass them into sut and coreDataStack variable that we declared before. After the tests finished, then the system will call
tearDownWithErrorfunction and we set the value of sut and coreDataStack to nil.
- Then, we will create our first test function to test Menu model. First we call
addMenufunction from sut and pass the return value into
newMenu. From here, we want to test that
newMenuis 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:
- First, create new variable called
backgroundContextand it will save the new background context given from our core data stack. We need to reinitialise the sut value and pass the
backgroundContextas an argument to RestoDataStore initializer parameter. Then, because we want to test async function so we create
expectationthat took two arguments. The first argument is notification type, in other word we want to test that
NSManagedObjectContextDidSavenotification is received. The second argument is the
- We ask
backgroundContextto perform operation for create new menu and save it. In this step, we also need to make sure that the
newMenuobject is not nil otherwise the test is failed.
- Finally we call
waitForExpectationswith the timeout of 2 second. If the
backgroundContextexecute the operation more than 2 second then the test is failed. Then the final test is make sure that
errorobject 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:
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
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.