{"id":32143168,"url":"https://github.com/ccavnor/mockcloudkitframework","last_synced_at":"2025-10-21T07:53:45.458Z","repository":{"id":63906790,"uuid":"479497206","full_name":"ccavnor/MockCloudKitFramework","owner":"ccavnor","description":"A framework for testing of CloudKit operations. It mocks CloudKit classes to provide a seamless way to test CloudKit operations in your App's code.","archived":false,"fork":false,"pushed_at":"2022-04-14T04:36:25.000Z","size":1437,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-21T07:53:33.520Z","etag":null,"topics":["cloudkit","icloud","integration-testing","ios-swift","mocking","swift","testing","uitesting","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ccavnor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-08T18:23:38.000Z","updated_at":"2025-04-16T12:37:25.000Z","dependencies_parsed_at":"2022-11-28T22:49:27.841Z","dependency_job_id":null,"html_url":"https://github.com/ccavnor/MockCloudKitFramework","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ccavnor/MockCloudKitFramework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccavnor%2FMockCloudKitFramework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccavnor%2FMockCloudKitFramework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccavnor%2FMockCloudKitFramework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccavnor%2FMockCloudKitFramework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ccavnor","download_url":"https://codeload.github.com/ccavnor/MockCloudKitFramework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccavnor%2FMockCloudKitFramework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280225806,"owners_count":26293888,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudkit","icloud","integration-testing","ios-swift","mocking","swift","testing","uitesting","unit-testing"],"created_at":"2025-10-21T07:53:43.051Z","updated_at":"2025-10-21T07:53:45.451Z","avatar_url":"https://github.com/ccavnor.png","language":"Swift","readme":"[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fccavnor%2FMockCloudKitFramework%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/ccavnor/MockCloudKitFramework)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fccavnor%2FMockCloudKitFramework%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/ccavnor/MockCloudKitFramework)\n\n# ``MockCloudKitFramework``\n\nA framework for testing of CloudKit operations. It mocks CloudKit classes to provide a seamless way to test CloudKit operations in your App's code.\n\n## Why do you need this?\nCloudKit is rich framework for shared records, but it resists testing strategies mainly because it hides its initializers from the developer - making it impossible to just create a test instance of CKContainer. CloudKit does offer a test environment that uses your app’s com.apple.developer.icloud-container-environment entitlement, but that is more of a sandbox for records than the API to manage them. \n\nMockCloudKitFramework attempts to fill this gap.\n\nThe two most vital classes of CloudKit, CKContainer and CKDatabase are (unfortunately) functionally implemented as finals. They can be subclassed but their init methods are not accessable. Therefore, MockCloudKitFramework cannot simply subclass CloudKit classes. \n\nPerhaps worse, they both inherit directly from NSObject as their common Protocol. So that IOC would force generic functions to be open to NSObject types - leaving functions wide open for injection of everything that inherits from NSObject.\n\nTo close this gap, MockCloudKitFramework (MCF) creates its own Protocols and extends CloudKit with mocks for the objects that it implements. \n\nHere is a movie of using a simple app that lets you type a message and post that message into iCloud. The UI has no idea that we are using MCF instead of CloudKit here:\n\n\u003cimg src=\"https://github.com/ccavnor/MockCloudKitFramework/blob/main/resources/successful_test.gif\" alt=\"testing with success conditions\" width=\"250\"/\u003e\n\n\nHowever, we can easily tell MCF that we want the transaction to fail with a certain error:\n\n\u003cimg src=\"https://github.com/ccavnor/MockCloudKitFramework/blob/main/resources/failure_test.gif\" alt=\"testing with failure conditions\" width=\"250\"/\u003e\n\n## Requirements\nMockCloudKitFramework is built and tested for iOS 15.0 and onward only. This is to take advantage of the cleaner implementation of CKDatabaseOperation functionality. However, some \"legacy\" (not deprecated as of yet, but the CloudKit documentation specifies alternative CKDatabaseOperations to use) methods from CKDatabase are included in the framework. Their implementation merely returns an error through their completion handler. I opted to not mark them as throwing because that would make the mocked methods conflict with the non-throwing signatures of their CloudKit counterparts. In general, I strived to maintain all method signatures exactly as CloudKit implements them.\n\n## Overview\nThe MockCloudKitFramework (MCF) implements mock operations for CKContainer and CKDatabase functionality mainly, but also mocks operations that inherit from CKDatabaseOperation. These comprise a big chunk of CloudKit interoperatability, but there are still areas of CloudKit functionality that are not mocked. \n\n\u003e Note: Zone operations are not handled, but more significantly the CloudKit asynchronous API operations added for Swift 5.5 async/await support have not (as of yet) been implemented in MockCloudKitFramework. \n\nOK, that's enough about what MockCloudKitFramework cannot or will not do. Lets take a look at what it _does_ do.\n\n## Using MockCloudKitFramework\n\u003e Tip: MockCloudKitFramework is designed to follow the API of CloudKit as closely as possible. So using it will be as familiar as using CloudKit itself. \n\n### Working Example\nBelow is a general example of how to proceed with MockCloudKitFramework. See project documentation and the accompanying MockCloudKitFrameworkTestProject for more details.\n\n##### A First Step\nLet's say that we have a View or View Controller that contains some code that calls the CloudKit API:\n```swift\nlet cloudContainer: CKContainer = CKContainer.default()\nlet database: CKDatabase = cloudContainer.publicCloudDatabase\n\n// CKAccountStatus codes are constants that indicate the availability of \n// the user’s iCloud account. Note that ONLY the return of CKAccountStatus.available\n// signifies that the user is signed into iCloud. Any other return value indicates an error.\nfunc accountStatus(completion: @escaping (Result\u003cCKAccountStatus, Error\u003e) -\u003e Void) {\n    cloudContainer.accountStatus { status, error in\n        switch status {\n        case .available:\n            completion(.success(.available))\n        default:\n                guard let error = error else {\n                    let error = NSError.init(domain: \"AccountStatusError\", code: 0) as Error\n                    completion(.failure(error))\n                    return\n                }\n            completion(.failure(error))\n        }\n    }\n}\n```\nThat's great, but how do we test when CloudKit responds with anything but CKAccountStatus.available? We could always turn off the wifi on the laptop to force CloudKit to respond with some other CKAccountStatus (you know you've done it).\n\nBut that's not exactly handy for testing. And testing should be deterministic and fast. Oh, and automatable.\n\n##### Test\nSince the object here is to test our code, lets take the calling code that we have in the view and put it into a test:\n\n```swift\nimport XCTest\nimport CloudKit\n@testable import OurProject // required for access to CloudController\n\nclass MockCloudKitTestProjectIntegrationTest: XCTestCase {\n    var cloudContainer: CKContainer!\n\n    /// Lookup table for CKAccountStatus codes\n    let ckAccountStatuses: [CKAccountStatus] = [\n        CKAccountStatus.couldNotDetermine,\n        CKAccountStatus.available,\n        CKAccountStatus.restricted,\n        CKAccountStatus.noAccount,\n        CKAccountStatus.temporarilyUnavailable\n    ]\n\n    override func setUpWithError() throws {\n        try? super.setUpWithError()\n        cloudContainer = CKContainer.default()\n    }\n\n    // ================================\n    // test accountStatus()\n    // ================================\n    // test that we get errors for all CKAccountStatus except for CKAccountStatus.available and that status is expected message.\n    func test_accountStatus() {\n        XCTAssertNotNil(cloudContainer)\n\n        for (status, message) in ckAccountStatusMessageMappings {\n            let expect = expectation(description: \"CKAccountStatus\")\n                cloudContainer.accountStatus { result in\n                switch result {\n                case .success:\n                    XCTAssertEqual(self.ckAccountStatusMessageMappings[status],\n                                   CKAccountStatusMessage.available.rawValue)\n                    expect.fulfill()\n                case .failure(let error):\n                    XCTAssertNotNil(error)\n                    XCTAssertEqual(self.ckAccountStatusMessageMappings[status], message)\n                    expect.fulfill()\n                }\n            }\n            waitForExpectations(timeout: 1)\n        }\n    }\n}\n```\nOur problem now is that the function is testable via unit test, but we are subject to the state of CloudKit to make the test pass. And we have little control over CloudKit state. This is where MockCloudKit framework comes in.\n\n##### Controller Class\nLet's define the following class, CloudController, in our project. CloudController is essentially a wrapper around the CloudKit API. It contains two methods:\n- accountStatus: calls CloudKit to get user account information. \n- checkCloudRecordExists: uses the CloudKit CKFetchRecordsOperation operation to find out if CloudKit has a certain record added to the database of whichever scope we pass in.\n\n\u003e Note: Note the use of Generics, here. The CloudController class is typed to an object that conforms to the CloudContainable protocol.\n\n```swift\nimport CloudKit\n\n/// Example Class to handle iCloud related transactions. \nclass CloudController\u003cT: CloudContainable\u003e {\n    let cloudContainer: T\n    let database: T.DatabaseType\n\n    init(container: T, databaseScope: CKDatabase.Scope) {\n        self.cloudContainer = container\n        self.database = container.database(with: databaseScope)\n    }\n\n    func accountStatus(completion: @escaping (Result\u003cCKAccountStatus, Error\u003e) -\u003e Void) {\n        cloudContainer.accountStatus { status, error in\n            switch status {\n            case .available:\n                completion(.success(.available))\n            default:\n                guard let error = error else {\n                    let error = NSError.init(\n                        domain: \"AccountStatusError\", \n                        code: 0) as Error\n                    completion(.failure(error))\n                    return\n                }\n                completion(.failure(error))\n            }\n        }\n    }\n\n/// Check if a record exists in iCloud.\n/// - Parameters:\n///   - recordId: the record id to locate\n///   - completion: closure to execute on caller\n/// - Returns: success(true) when record is located, success(false) when record is \n///   not found, failure if an error occurred.\nfunc checkCloudRecordExists(recordId: CKRecord.ID, \n                            _ completion: @escaping (Result\u003cBool, Error\u003e) -\u003e Void) {\n        let dbOperation = CKFetchRecordsOperation(recordIDs: [recordId])\n        dbOperation.recordIDs = [recordId]\n        var record: CKRecord?\n        dbOperation.desiredKeys = [\"recordID\"]\n        // perRecordResultBlock doesn't get called if the record doesn't exist\n        dbOperation.perRecordResultBlock = { _, result in\n            // success iff no partial failure\n            switch result {\n            case .success(let r):\n                record = r\n            case .failure:\n                record = nil\n            }\n        }\n        // fetchRecordsResultBlock always gets called when finished processing.\n        dbOperation.fetchRecordsResultBlock = { result in\n            // success if no transaction error\n            switch result {\n            case .success():\n                if let _ = record { // record exists and no errors\n                    completion(.success(true))\n                } else { // record does not exist\n                    completion(.success(false))\n                }\n            case .failure(let error): // either transaction or partial failure occurred\n                completion(.failure(error))\n            }\n        }\n        database.add(dbOperation)\n    }\n}\n```\n##### Another pass at testing\nNow that we have our CloudController set up for Generics, lets redefine our test, this time using MockCloudKitFramework. \n\n\u003e Note: Note the use of the setError property on MockCKContainer to set the fail condition for MCF's MockCKContainer. \n\nNow, CloudController's accountStatus method will return success only for CKAccountStatus.available. Boom. Testable.\n\n```swift\n    // ================================\n    // test accountStatus()\n    // ================================\n    // test that we get errors for all CKAccountStatus except for \n    // CKAccountStatus.available and that status is expected message.\n    func test_accountStatus() {\n        XCTAssertNotNil(cloudContainer)\n\n        for (status, message) in ckAccountStatusMessageMappings {\n            let expect = expectation(description: \"CKAccountStatus\")\n            // NOTE that we are setting both success (.available) and error statuses \n            // (all others) on MockCKContainer now\n            cloudContainer.setAccountStatus = status\n            cloudController.accountStatus { result in\n                switch result {\n                case .success:\n                    XCTAssertEqual(self.ckAccountStatusMessageMappings[status],\n                                   CKAccountStatusMessage.available.rawValue)\n                case .failure(let error):\n                    XCTAssertNotNil(error)\n                    XCTAssertEqual(self.ckAccountStatusMessageMappings[status], message)\n                }\n                expect.fulfill()\n            }\n            waitForExpectations(timeout: 1)\n        }\n    }\n}\n```\n\n##### Test CKFetchRecordsOperation operation on CKDatabase\nOk, lets see what else we can do. \n\n###### Test CKFetchRecordsOperation success\nSuppose that we wanted to check if a record exists in the public scope of our CloudKit database? Well, CloudController has the checkCloudRecordExists() method that calls through CloudKit's CKFetchRecordsOperation operation to fetch records. But what record do we check for? MockCloudKitFramework has you covered. With it, we can set records on a local (mocked) instance of CKDatabase (MockCKDatabase) and again inject our mock CKContainer into CloudController.\n\n```swift\nfunc test_checkCloudRecordExists_success() {\n    let expect = expectation(description: \"CKDatabase fetch\")\n    let record = makeCKRecord()\n    // First, add the record to MockCKDatabase\n    cloudDatabase.addRecords(records: [record])\n    // Then check for its existence\n    cloudController.checkCloudRecordExists(recordId: record.recordID) { result in\n        switch result {\n        case .success(let exists):\n            XCTAssertTrue(exists)\n            expect.fulfill()\n        case .failure:\n            XCTFail(\"failure only when error occurs\")\n        }\n    }\n    waitForExpectations(timeout: 1)\n}\n```\nThat's it!! All we had to do is add the record to MockCKDatabase (the MCF version of the CloudKit CKContainer class) and then call checkCloudRecordExists(). Note that it would have been perfectly fine to use a CKModifyRecordsOperation operation to add the records to MockCKDatabase first (this is what we would do when dealing with CloudKit), but the ``MockCKDatabase`` API lets us mutate the database simply.\n\nLet's test it again, but this time set the error that we want CloudKit to fail with:\n\n```swift\n// call checkCloudRecordExists() when the record is present but error is set\nfunc test_checkCloudRecordExists_error() {\n    let expect = expectation(description: \"CKDatabase fetch\")\n    let record = makeCKRecord()\n    cloudDatabase.addRecords(records: [record])\n    // set an error on operation\n    let nsErr = createNSError(with: CKError.Code.internalError)\n    MockCKDatabaseOperation.setError = nsErr\n    cloudController.checkCloudRecordExists(recordId: record.recordID) { result in\n        switch result {\n        case .success:\n            XCTFail(\"should have failed\")\n            expect.fulfill()\n        case .failure(let error):\n            XCTAssertEqual(error.createCKError().code.rawValue, nsErr.code)\n            expect.fulfill()\n        }\n    }\n    waitForExpectations(timeout: 1)\n}\n```\n\nThe only difference here is that we created an NSError and added it to our MockCKDatabaseOperation via the static setError property:\n\n```swift\nlet nsErr = createNSError(with: CKError.Code.internalError)\nMockCKDatabaseOperation.setError = nsErr\n```\n\nWe can even test our function logic for [partial failures](https://developer.apple.com/documentation/cloudkit/ckerror/2325226-partialfailure) to make sure that we handle the scenario of when a record _might_ be found but some CKError occurred so we cannot be sure. All we need to do is set the setRecordErrors property on the operation to the set of record ids that should fail (MCF picks a random CKError to fail with):\n\n```swift\n// test for partial failures\nfunc test_checkCloudRecordExists_partial_error() {\n    let expect = expectation(description: \"CKDatabase fetch\")\n    let record = makeCKRecord()\n    cloudDatabase.addRecords(records: [record])\n    // set an error on Record\n    MockCKFetchRecordsOperation.setRecordErrors = [record.recordID]\n    cloudController.checkCloudRecordExists(recordId: record.recordID) { result in\n        switch result {\n        case .success:\n            XCTFail(\"should have failed\")\n        case .failure(let error):\n            let ckError = error.createCKError()\n            XCTAssertEqual(ckError.code.rawValue,\n                           CKError.partialFailure.rawValue,\n                           \"The transaction error should always be set to CKError.partialFailure when record errors occur\")\n            if let partialErr: NSDictionary = error.createCKError().getPartialErrors() {\n                let ckErr = partialErr.allValues.first as? CKError\n                XCTAssertEqual(\"CKErrorDomain\", ckErr?.toNSError().domain)\n                expect.fulfill()\n            }\n        }\n    }\n    waitForExpectations(timeout: 1)\n}\n```\n\n## Installation\n\n#### Import MockCloudKitFramework.framework to your project\nAdding to your project is simple via the [Swift Package Manager](https://www.swift.org/package-manager/). From XCode just choose File -\u003e Add packages... and point to this repository. Make sure that the project is installed as a Framework (check Project Settings -\u003e General -\u003e My Target -\u003e Frameworks, Libraries, and Embedded Content). \n\nThe MockCloudKitTestFramework (the XCode project that provides examples of unit, integration, and UITesing of MockCloudKitFramework ) can be cloned and run as a standard XCode project.\n\n## Setup\nSetting up MockCloudKitFramework (MCF) can be done in at least two ways. The first is simple but potentially not safe for production. The second requires a few more steps. Both will require that you use generics to pass in the CloudKit or MCF classes that you implement via IOC (dependency injection). More on that later.\n\n#### The easy way\nJust import the framework as:\n```swift\nimport MockCloudKitFramework\n```\nThat's all it takes. But the tradeoff is that you must import MCF everywhere that you import CloudKit (assuming that you want to test that module). That might be offputting to some developers. But keep in mind that all MCF code (including these protocols and their extensions) are wrapped in `#if DEBUG` pragma - so that nothing is exposed during normal runtime, only during test runs.\nBut if you want to avoid the risk of importing a test dependency into production code, see the next section. \n\n#### The (slightly) harder way\nYou can use MCF purely from your test classes. You'll just have to load the MCF protocols and their extensions into your respective targets (XCode maintains seperate environments for each target). Its up to you how and when to expose the MCF protocols and extensions, but the recommended way is to wrap them in a `#if DEBUG` block minimally. That will ensure that they are only loaded during test runs and that they will be stripped from production code via the compiler.\n\n##### Install MCF protocols\nCopy the following set of Protocols into a module in your project (NOT test) target. A good place might be your root app module (see MockCloudKitTestProject/MockCloudKitTestProjectApp.app for an example): \n\n```swift\n# if DEBUG\n// ========================================\n// MockCloudKitFramework Protocols\n// ========================================\n/// Protocol for CKFetchRecordsOperation interoperability\npublic protocol CKFetchRecordsOperational: DatabaseOperational {\n    var recordIDs: [CKRecord.ID]? { get set }\n    var desiredKeys: [CKRecord.FieldKey]? { get set }\n    // `CKDatabaseOperation`s:\n    /// The closure to execute with progress information for individual records\n    var perRecordProgressBlock: ((CKRecord.ID, Double) -\u003e Void)? { get set }\n    /// The closure to execute after CloudKit modifies all of the records\n    var fetchRecordsResultBlock: ((Result\u003cVoid, Error\u003e) -\u003e Void)? { get set }\n    /// The closure to execute once for every fetched record\n    var perRecordResultBlock: ((CKRecord.ID, Result\u003cCKRecord, Error\u003e) -\u003e Void)? { get set }\n}\n/// Protocol for CKQueryOperation interoperability\npublic protocol CKQueryOperational: DatabaseOperational {\n    var query: CKQuery? { get set }\n    var desiredKeys: [CKRecord.FieldKey]? { get set }\n    // `CKDatabaseOperation`s:\n    /// The closure to execute after CloudKit modifies all of the records\n    var queryResultBlock: ((_ operationResult: Result\u003cCKQueryOperation.Cursor?, Error\u003e) -\u003e Void)? { get set }\n    /// The closure to execute once for every fetched record\n    var recordMatchedBlock: ((_ recordID: CKRecord.ID, _ recordResult: Result\u003cCKRecord, Error\u003e) -\u003e Void)? { get set }\n}\n/// Protocol for CKModifyRecordsOperation interoperability\npublic protocol CKModifyRecordsOperational: DatabaseOperational {\n    var recordsToSave: [CKRecord]? { get set }\n    var recordIDsToDelete: [CKRecord.ID]? { get set }\n    var savePolicy: CKModifyRecordsOperation.RecordSavePolicy { get set }\n    // `CKDatabaseOperation`s:\n    /// The closure to execute with progress information for individual records\n    var perRecordProgressBlock: ((CKRecord, Double) -\u003e Void)? { get set }\n    /// The closure to execute after CloudKit modifies all of the records\n    var modifyRecordsResultBlock: ((_ operationResult: Result\u003cVoid, Error\u003e) -\u003e Void)? { get set }\n    /// The closure to execute once for every deleted record\n    var perRecordDeleteBlock: ((_ recordID: CKRecord.ID, _ deleteResult: Result\u003cVoid, Error\u003e) -\u003e Void)? { get set }\n    /// The closure to execute once for every saved record\n    var perRecordSaveBlock: ((_ recordID: CKRecord.ID, _ saveResult: Result\u003cCKRecord, Error\u003e) -\u003e Void)? { get set }\n}\n/// Shadow protocol to bridge CKDatabaseOperationProtocol.OperationType ==\u003e CKContainerProtocol.DatabaseType.OperationType\npublic protocol AnyCKDatabaseProtocol {\n    /// - Receives a parameter of Concrete Type `Any`\n    func add(_ operation: Any)\n}\n/// Protocol for CKDatabase interoperability\n/// Uses `AnyCKDatabaseProtocol` shadow protocol for type conversion. This acts as a bridge between CloudStorable\n/// and the operations that extend DatabaseOperational to a common OperationType.\npublic protocol CloudStorable: AnyCKDatabaseProtocol {\n    associatedtype OperationType: DatabaseOperational\n    /// Keep track of last executed query for testing purposes\n    var lastExecuted: MockCKDatabaseOperation? { get set }\n    /// - Receives a parameter of Concrete Type that is a `DatabaseOperational`\n    func add(_ operation: OperationType)\n}\n/// Default extension to conform to `DatabaseOperational` by using `AnyCKDatabaseProtocol` for type erasure\nextension CloudStorable {\n    public func add(_ operation: Any) {\n        // ensure that we partition CloudKit operations from MCF ones\n        if let operation = operation as? OperationType {\n            add(operation)\n        } else {\n            // convert CKDatabaseOperation types to MockCKDatabaseOperation (but never the opposite)\n            let mockDB = self as! MockCloudKitFramework.MockCKDatabase\n            if let ckDatabaseOperation = operation as? CKFetchRecordsOperation {\n                let mockOp = ckDatabaseOperation.getMock(database: mockDB)\n                add(mockOp)\n            } else if let ckDatabaseOperation = operation as? CKQueryOperation {\n                let mockOp = ckDatabaseOperation.getMock(database: mockDB)\n                 add(mockOp)\n            } else if let ckDatabaseOperation = operation as? CKModifyRecordsOperation {\n                let mockOp = ckDatabaseOperation.getMock(database: mockDB)\n                 add(mockOp)\n            } else {\n                fatalError(\"Unknown operation attempted to convert to its mock counterpart: \\(operation)\")\n            }\n        }\n    }\n}\n/// Used only for NSObject conformance so that we can use Key-Value Coding\npublic protocol DatabaseOperational: NSObject {\n    associatedtype DatabaseType: CloudStorable\n    var database: DatabaseType? { get set }\n    /// The operation's configuration - inherited from `CKOperation`\n    var configuration: CKOperation.Configuration! { get set }\n    /// The custom completion block. Always the last block to be called. inherited from `Operation`\n    var completionBlock: (() -\u003e Void)? { get set }\n}\nextension MockCKDatabaseOperation {\n    public typealias DatabaseType = MockCKDatabase\n}\n/// Protocol for CKContainer interoperability\npublic protocol CloudContainable {\n    associatedtype DatabaseType: CloudStorable\n    var containerIdentifier: String? { get }\n    func database(with databaseScope: CKDatabase.Scope) -\u003e DatabaseType\n    func accountStatus(completionHandler: @escaping (CKAccountStatus, Error?) -\u003e Void)\n    func fetchUserRecordID(completionHandler: @escaping (CKRecord.ID?, Error?) -\u003e Void)\n}\n#endif\n```\n##### Protocol extensions\nThen copy the following protocol extension into the same module. This extends CloudKit with a common set of Protocols as MCF:\n```swift\n# if DEBUG\n// ========================================\n// MARK: CloudKit MCF protocol extensions\n// ========================================\n// These extensions make CloudKit comply with MCF protocols\nextension CKContainer: CloudContainable {}\nextension CKDatabase: CloudStorable {\n    // only for state tracking in mock operations\n    public var lastExecuted: MockCKDatabaseOperation? {\n        get {\n            return nil\n        }\n        set(newValue) {\n            // nothing to do\n        }\n    }\n}\nextension CKDatabaseOperation: DatabaseOperational {\n    public typealias DatabaseType = CKDatabase\n}\nextension CKFetchRecordsOperation: CKFetchRecordsOperational {}\nextension CKQueryOperation: CKQueryOperational {}\nextension CKModifyRecordsOperation: CKModifyRecordsOperational {}\n// ====================== CloudKit MCF protocol extensions\n#endif\n```\n\n##### Setting up test target\n\nYour project and test targets don't share environments, so all you need to do is import MCF into your test class:\n```swift\nimport MockCloudKitFramework\n```\n\n##### Setting up for IOC\nThe classes, structs and methods that call CloudKit must be implemented as Generics. More precisely, if you examine the set of Protocols and Protocol extensions, you will see that the following set of CloudKit classes (and their MCF mock counterparts) must be typed as their designated Protocol:\n\n\n| Protocol | CloudKit class name | MCF class name |\n| -------------------------- | ------------------------- | ---------------------------- |\n| CloudContainable           |  CKContainer              | MockCKContainer               |\n| CloudStorable              |  CKDatabase               |  MockCKDatabase               |\n| DatabaseOperational        |  CKDatabaseOperation      |  MockCKDatabaseOperation      |\n| CKModifyRecordsOperational |  CKModifyRecordsOperation |  MockCKModifyRecordsOperation |\n| CKFetchRecordsOperational  |  CKFetchRecordsOperation  |  MockCKFetchRecordsOperation  |\n| CKQueryOperational         |  CKQueryOperation         |  MockCKQueryOperation         |\n\nTherefore, you might have a Generic class that accepts a CKContainer or MockCKContainer via their common Protocol, CloudContainable:\n```swift\nclass CloudController\u003cContainer: CloudContainable\u003e: ObservableObject {\n    let cloudContainer: Container\n    let database: Container.DatabaseType\n\n    init(container: Container, databaseScope: CKDatabase.Scope) {\n        self.cloudContainer = container\n        self.database = container.database(with: databaseScope)\n    }\n```\n\u003e Tip: Nothing at all needs to change for any methods! MCF converts the CloudKit operations to MCF operations in the background.\n\nThat being said, you can always inject an operation into a generic method (maybe because MCF doesn't support some functionality of a given operation).\nHere, we can pass in either a CKFetchRecordsOperation or a MCF MockCKFetchRecordsOperation - they both conform to CKFetchRecordsOperational.\n\n```swift\nfunc doSomething\u003cO: CKFetchRecordsOperational\u003e (\n    cKFetchRecordsOperation: O,\n    _ completion: @escaping (Bool) -\u003e Void) {\n        var dbOperation = cKFetchRecordsOperation\n\n        // fetchRecordsResultBlock always gets called when finished processing.\n        dbOperation.fetchRecordsResultBlock = { result in\n            if let _ = record {\n                completion(true)\n            } else {\n                completion(false)\n            }\n        }\n\n        database.add(dbOperation)\n    }\n}\n```\n\n## More Examples\nSee __MockCloudKitTestProject__, the associated test project to MockCloudKitFramework, for Unit and Integration tests of MockCloudKitFramework for multiple examples (with documentation) of how to use MockCloudKitFramework in your project.\n\n## Attribution\nThe following resources gave me the necessary background knowledge to build MCF:\n\nThis post informed my thinking of how to mock CloudKit objects:\n- [Simulating CloudKit Errors](https://crunchybagel.com/simulating-cloudkit-errors/) by Quentin Zervaas. \n\nThese resources helped to sort out the handling of CloudKit errors:\n- [StackOverflow post by Wael Showair](https://stackoverflow.com/a/49691453/4698449)\n- [StackOverflow post by Thunk](https://stackoverflow.com/a/43575025/4698449)\n\nUtility for converting DocC archives to static Websites:\n- [docc2html](https://github.com/DoccZz/docc2html)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccavnor%2Fmockcloudkitframework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fccavnor%2Fmockcloudkitframework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccavnor%2Fmockcloudkitframework/lists"}