I'm having some trouble figuring out how to make mocks/stubs for CloudKit and Core Data.
I have made some progress with my CloudKit service layer by injecting a database which conforms to a protocol I've written that essentially overrides CKDatabase functions.
/// A protocol to allow mocking a CKDatabase.
protocol CKDatabaseProtocol {
func add(_ operation: CKDatabaseOperation)
func delete(withRecordID recordID: CKRecord.ID, completionHandler: @escaping (CKRecord.ID?, Error?) -> Void)
func fetch(withRecordID recordID: CKRecord.ID, completionHandler: @escaping (CKRecord?, Error?) -> Void)
func perform(_ query: CKQuery, inZoneWith zoneID: CKRecordZone.ID?, completionHandler: @escaping ([CKRecord]?, Error?) -> Void)
func save(_ record: CKRecord, completionHandler: @escaping (CKRecord?, Error?) -> Void)
}
extension CKDatabase: CKDatabaseProtocol { }
With this, I can inject the real CKContainer.default().publicCloudDatabase into my service or I can create a mock class that conforms to the same protocol and inject my MockCKDatabase into my unit tests service instance. This works for all the functionality except the add(operation) function. I'm not sure how to get a CKQueryOperation added to my MockCKDatabase to have its completion blocks triggered.
For the Core Data portion, I am using the new NSPersistentCloudKitContainer to sync the user's private database (while using my CloudKit service to make queries to my public database). I found an excellent blog about creating a Core Data stack that allows you to inject the store type when setting up the stack so that you can use NSSQLiteStoreType in production and NSInMemoryStoreType in testing.
However, when I try to use the in-memory solution I get the following error:
"NSLocalizedFailureReason" : "CloudKit integration is only supported for SQLite stores."
Is there some better solution to test CloudKit/Core Data? I would really like to have my service layers thoroughly tested.
I'm not sure how to get a CKQueryOperation added to my MockCKDatabase to have its completion blocks triggered.
You have to implement the operation's behavior yourself. Write a switch statement with a case for each operation subclass that you use. In each case look at the properties of the operation, do the requested behavior, and then call the callback with the appropriate result or error.
I'm halfway through doing this right now, but running into roadblocks. Handling CKFetchRecordZoneChangesOperation involves returning a CKServerChangeToken instance, but there's no way to create such an object since it has no public constructor. And when fetching records, there's no way to create CKRecords with specified dates and changeTags.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With