Trying to mock CKDatabase but it has no accessible initializer. Is there another way to create a fake CKDatabase object?
This code comes back with the "error: cannot override 'init' which has been marked unavailable"
class MockDatabase : CKDatabase
{
override func saveRecord(record: CKRecord!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecordID!, NSError!) -> Void)!) { }
override func fetchRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteSubscriptionWithID(subscriptionID: String!, completionHandler: ((String!, NSError!) -> Void)!) { }
override func saveSubscription(subscription: CKSubscription!, completionHandler: ((CKSubscription!, NSError!) -> Void)!) {}
}
The cloud kit framework seems like a perfect candidate for unit testing as there are a lot of possible errors that can come back from the server and making sure your app can handle them all consistently , with every iteration would be great. Has anyone found a way around this?
Since CKDatabase init is made unavailable, you will never be able to create an instance of it. So there is no point in creating your MockDatabase because you will never be able to create an instance of that. The only way to do some sort of Mocking is by creating your own DAO class wrapper around the CKDatabase functionality and only call CKDatabase functions using that DAO. Then you could mock that DAO. You could even add extra functionality to that DAO, but then you will not be able to test that.
All the CloudKit classes are #objc (they all derive from NSObject.) That means they use dynamic dispatching. In Objective-C you could mock CKDatabase by implementing a new Obj-C class MockCKDatabase that subclasses NSObject and implements all the public methods of CKDatabase (or at least all the ones you use.) Then you literally just instantiate one and cast the pointer from MockCKDatabase* to CKDatabase*.
I've never done this sort of thing in Swift, but I imagine that if you just did the same steps it should work.
as #Edwin pointed out, CKDatabase init is made unavailable, so you can't make instances of it. Neither can you subclass or use an extension to override CKContainer init (compiler will complain that 'init()' is unavailable). You can't even use a CKContainer with a mock of CKDatabase, since the logic that associates them is hidden within the CloudKit implementation. All that said, I built a framework called MockCloudKitFramework that is a drop-in replacement for a limited (but reasonable) subset of CloudKit capabilities. The MockCloudKitFramework (MCF) implements mock operations for CKContainer and CKDatabase, plus the main subclasses of CKDatabaseOperation.
MCF creates its own protocols (that contain the same signatures as their CloudKit counterparts) and extends both CloudKit and MCF mocks from this set of protocols. It was necessary to create these protocols since CKContainer and CKDatabase 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.
So using MCF comes down to:
Extending CloudKit and MCF classes from these protocols.
Refactoring your code to use generics - typed on the MCF protocols.
Using IOC to inject the mocks of CKContainer and CKDatabase (MockCKContainer and MockCKDatabase, respectively).
See the MCF docs - examples are provided, along with unit and integration tests.
Using MCF
Lets say that you have a generic class that takes a type based on the CKContainerProtocol protocol (one of the MCF protocols). It contains a method that accepts either a CKFetchRecordsOperation or a MockCKFetchRecordsOperation (the MCF mock of CKFetchRecordsOperation) based on their common MCF protocol, CKFetchRecordsOperationProtocol:
import CloudKit
/// Example Class to handle iCloud related transactions.
class CloudController<T: CKContainerProtocol> {
let cloudContainer: T
let database: T.DatabaseType
init(container: T, databaseScope: CKDatabase.Scope) {
self.cloudContainer = container
self.database = container.database(with: databaseScope)
}
/// Check if a record exists in iCloud.
/// - Parameters:
/// - cKFetchRecordsOperation: the fetch operation
/// - recordId: the record id to locate
/// - completion: closure to execute on caller
/// - Returns: success(true) when record is located, success(false) when record is not found, failure if an error occurred.
func checkCloudRecordExists<O: CKFetchRecordsOperationProtocol> (
cKFetchRecordsOperation: O,
recordId: CKRecord.ID,
_ completion: #escaping (Result<Bool, Error>) -> Void) {
var dbOperation = cKFetchRecordsOperation
dbOperation.recordIDs = [recordId]
var record: CKRecord?
dbOperation.desiredKeys = ["recordID"]
// perRecordResultBlock doesn't get called if the record doesn't exist
dbOperation.perRecordResultBlock = { _, result in
// success iff no partial failure
switch result {
case .success(let r):
record = r
case .failure:
record = nil
}
}
// fetchRecordsResultBlock always gets called when finished processing.
dbOperation.fetchRecordsResultBlock = { result in
// success if no transaction error
switch result {
case .success:
if let _ = record { // record exists and no errors
completion(.success(true))
} else { // record does not exist
completion(.success(false))
}
case .failure(let error): // either transaction or partial failure occurred
completion(.failure(error))
}
}
database.add(dbOperation)
}
}
We can build a test without MCF that validates that the record does (or doesn't) exist, but first we have to:
Populate (or depopulate) the CloudKit test environment with the data to set the test up.
Make sure that we are using the same CKDatabase scope (public, private, or shared) as where the data is.
Hope that there are no network hiccups or data transfer errors that will inject unanticipated errors or interruptions.
But still, we can't test what the error conditions might be since we have no control over them.
MCF allows us to test all of this, using a local mock instance of CKDatabase (MockCKDatabase) that we can manipulate directly or through CloudKit modification operations like (Mock)CKModifyRecordsOperation. MockCKContainer is used to access the MockCKDatabase instance of the appropriate scope using the same accessors as CKContainer does. Here is an example of a test case setup:
import XCTest
import CloudKit
#testable import OurProject // required for access to CloudController
import MockCloudKitFramework // import MCF only into test environment
class MockCloudKitTestProjectIntegrationTest: XCTestCase {
var cloudContainer: MockCKContainer!
var cloudDatabase: MockCKDatabase!
// NOTE that CloudController class is a Generic and that both CKContainer and MockCKContainer extend CKContainerProtocol
var cloudController: CloudController<MockCKContainer>!
// called before each test
override func setUpWithError() throws {
try? super.setUpWithError()
// reset state for each test. Each test must set up exactly the state they require.
MockCKContainer.resetContainer()
// get the default container from MCF
cloudContainer = MockCKContainer.default()
// get the MockCKDatabase of CKDatabase.Scope of private
cloudDatabase = cloudContainer.privateCloudDatabase
// we pass in a MockCKContainer to the generic function expecting a class that extends MCF CKContainerProtocol
cloudController = CloudController(container: cloudContainer, databaseScope: .private)
}
}
Now we can test the checkCloudRecordExists() method by adding the record that we expect to fetch directly to MockCKDatabase and calling the function, passing in our mock CKFetchRecordsOperation:
func test_checkCloudRecordExists_success() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord() // function that makes a CKRecord
// add the record to MockCKDatabase
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// pass in our mock CKFetchRecordsOperation
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { exists in
XCTAssertTrue(exists)
expect.fulfill()
}
waitForExpectations(timeout: 1)
}
If we wanted to test an error condition from the call, we just need to add an error using the setError property of MockCKDatabaseOperation (or any of its subclasses). Now the test will fail, even though the record exists. This simulates an internal error that was the result of a CloudKit transaction:
// call checkCloudRecordExists() when the record is present but error is set
func test_checkCloudRecordExists_error() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
let nsErr = createNSError(with: CKError.Code.internalError)
operation.setError = nsErr
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
expect.fulfill()
case .failure(let error):
XCTAssertEqual(error.createCKError().code.rawValue, nsErr.code)
expect.fulfill()
}
}
waitForExpectations(timeout: 1)
}
We can even test our function logic for partial failures 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):
// test for partial failures
func test_checkCloudRecordExists_partial_failure() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
operation.setRecordErrors = [record.recordID]
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
case .failure(let error):
let ckError = error.createCKError()
XCTAssertEqual(ckError.code.rawValue,
CKError.partialFailure.rawValue,
"The transaction error should always be set to CKError.partialFailure when record errors occur")
if let partialErr: NSDictionary = error.createCKError().getPartialErrors() {
let ckErr = partialErr.allValues.first as? CKError
XCTAssertEqual("CKErrorDomain", ckErr?.toNSError().domain)
expect.fulfill()
}
}
}
waitForExpectations(timeout: 1)
}
Related
I am not able to fetch data in my IntentHandler class. My Goal is select Todo from the CoreData list and display it in Widget.
I am trying to display a list from CoreData in Widget Intent and I am expecting to resolve this issue.
extension IntentHandler : ConfigurationIntentHandling {
func provideTodoNameOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: #escaping (INObjectCollection<TodoData>?, Error?) -> Void) {
var arrTodoData = [TodoData]()
coreDH.getAllTodos().forEach { todos in
let todoIntent = TodoData(identifier: todos.id?.uuidString, display: todos.name ?? "")
arrTodoData.append(todoIntent)
}
let collection = INObjectCollection(items: arrTodoData)
completion(collection, nil)
}
}
class IntentHandler: INExtension{
let coreDH = CoreDataHandler.shared
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
}
If your IntentHandler doesn't get called:
Ensure your Widget uses IntentConfiguration not StaticConfiguration
Ensure your time line provider conforms to IntentTimelineProvider
Run the app scheme, then run the intent scheme, you should be able to debug and breakpoints would work.
Widgets Code-along, part 3: Advancing timelines (5:31)
Add Configuration and Intelligence to Your Widgets
If you need to share data between your app and extension:
App's data is sandboxed and is not accessible by extension
Configure App Groups and you could create core data file in the shared container to be able to access in your extension
If App and Extension use exactly the same data you could use the same sqlite file in the shared container (both app and extension would have access to it)
If App and Extension use different data and there is only a small portion that is common, then use History Tracking
Configuring App Groups
Consuming Relevant Store Changes
I want to write a golang function which return the service according to the input:
func GetService(service string) <what to write here> {
session, _ := session.NewSession(&aws.Config{Region: aws.String(Region)})
switch {
case service == 'ecr':
var svc *ecr.ECR
svc = ecr.New(session)
return svc
case service == 'ecs':
var svc *ecs.ECS
svc = ecs.New(session)
return svc
}
}
It is kind of factory for aws services, but what is their common type which has to be returned?
What you can do is write two functions where each will return either of these.
Other option is to return an interface and let the user cast the value as he knows what it is. Optionally interface can have some methods defined if object share methods with same name.
Last approach that I see is most fitting is a modular approach that I will also demonstrate. But with pseudo code.
func NewSession
session, err = (create session)
(handle error)
return session
// now lets use this
func MyUseCase
ecr = NewEcr(NewSession())
(use ecr)
The thing you are abstracting in your code should not be abstracted at all. User should create his instance as it is simpler then casting it and even faster.
The main goal of abstraction is simplify top level code and eliminate repetition. Keep that in mind.
UserController:
class UserController(private val graphRepository: GraphRepository) : Controller {
override fun installRoutes(router: Router) {
router.install {
post("/api/v1/user").handler(this#UserController::addUser)
}
}
}
Testing route and calling route handler "addUser":
#Test
fun newUserAdded() {
Mockito.`when`(mockRoutingContext.queryParam("id")).thenReturn(listOf("1"))
Mockito.`when`(mockGraphRepository.getUser("1")).thenReturn(Promise.ofSuccess(null))
Mockito.`when`(mockGraphRepository.enrollUser(any())).thenReturn(Promise.ofSuccess(Unit))
Mockito.`when`(mockRoutingContext.response()).thenReturn(mockHttpServerResponse)
Mockito.doNothing().`when`(mockHttpServerResponse).end()
UserController(mockGraphRepository).addUser(mockRoutingContext)
Mockito.verify(mockRoutingContext, Mockito.times(1)).response()
Mockito.verify(mockHttpServerResponse).end()
}
The main question is how to test the controller route without explicitly calling "addUser" on "UserController" because I want to make the controller function private.
Mocking behavior for types you don't own is generally discouraged for a variety of reasons, such as (but not limited to):
If the real implementation of the mocked dependency changes, the mock's behavior will not automatically reveal any forward-breaking changes.
The more mocks a test introduces, the more cognitive load the test carries, and some tests require a lot of mocks in order to work.
The approach that works best for me is to think of these more as integration tests, and avoid the mocks all together.
To achieve this, I've got an abstract VertxPlatform class that I extend that contains references to resources I commonly refer to across a variety of tests:
the Vertx instance itself
a Router
an EventBus
an HttpServer
a WebClient
These resources is reinitialized per invocation of each test, and the Router is subsequently associated with the HttpServer.
A typical test ends up looking something like this:
class MyHandlerIT : VertxPlatform() {
private lateinit var myHandler: MyHandler // <-- the component under test
#Before override fun setUp(context: TestContext) {
super.setUp(context) // <-- reinitializes all the underlying Vert.x components
myHandler = MyHandler()
router.post("/my/handler/path")
.handler(myHandler.validationHandler())
.handler(myHandler.requestHandler(vertx))
.failureHandler(myHandler.failureHandler())
}
#After override fun tearDown(context: TestContext) {
super.tearDown(context)
}
#Test fun status_400_on_some_condition(context: TestContext) {
val async = context.async()
testRequest(POST, path = "/my/handler/path", params = null, body = null, headers = null)
.subscribeBy(
onSuccess = { response ->
context.assertEquals(BAD_REQUEST.code(), response.statusCode())
async.complete()
},
onError = { error ->
context.fail(error)
}
)
}
}
In each individual test you might have some more case-specific setup. For example, if MyHandler gets results from your GraphRepository via the EventBus you could setup a fake Consumer within the scope of that test that replies with a pre-canned result that server back the values you were otherwise trying to mock.
Hope this helps, or at least inspires some thought!
So I have the following code:
When("SMS with location update command is received") {
every {
context.getString(R.string.location_sms, any(), any(), any(), any())
} returns "loc"
mainServiceViewModel.handleSms(SmsMessage("123", "location"))
Then("SMS with location is sent to specified phone number") {
verify(exactly = 1) {
smsRepository.sendSms("+123", "loc")
}
}
}
When("motion is detected") {
Then("information SMS is sent to specified phone number") {
verify(exactly = 1) {
smsRepository.sendSms("+123", any())
}
}
}
The problem with it, that both cases pass, even though the second one doesn't do any action. I expect second case to fail, as sendSms method is not even called.
How to reset smsRepository verify count?
How to reset that count before every When case?
This is probably due to the fact that KotlinTest is different from JUnit in what is considered a test and when a Spec instance is created.
The default behavior for KotlinTest is to create a single instance of the Spec per execution. Due to this, your mocks are not getting reset between executions, as you probably have them created at class level.
To fix this, what you can do is either do the mockk inside the test, or change the isolation mode to something that creates the Spec every time a test is executed.
The default isolationMode is IsolationMode.SingleInstance. You can change it on the Spec itself by overriding the isolationMode function:
class MySpec : BehaviorSpec() {
init {
Given("XX") {
Then("YY") // ...
}
}
override fun isolationMode() = IsolationMode.InstancePerTest
}
You can also change it in a ProjectConfig. If you need explanation on how to do it there, check the docs on ProjectConfig
An alternative would be to clear mocks on the afterTest method:
class MySpec : BehaviorSpec() {
init {
Given("XX") {
Then("YY") // ...
}
}
override fun afterTest(testCase: TestCase, result: TestResult) {
clearAllMocks()
}
}
But I'm not sure how that would work in your use-case.
You should try the various clear methods that are provided to reset the state of mocks. Check this related question and MockK's docs for more information.
Check the documentation about test listeners. Basically, every test spec class provides lifecycle methods like beforeEach, which you could override to reset your mocks (using clear). As you are extending BehaviourSpec, you should be able to just override those methods, otherwise confirm how to do this for different testing styles to avoid confusion.
To clear mocks after every test, you can provide a project wide listener:
import io.kotest.core.listeners.TestListener
import io.kotest.core.spec.AutoScan
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import io.mockk.clearAllMocks
#AutoScan
class MockkClearingTestListener : TestListener {
override suspend fun afterEach(testCase: TestCase, result: TestResult) = clearAllMocks()
}
This works e.g. for every leaf in a WordSpec, and should work for BehaviorSpec as well.
I have an ExampleModel that calls to an ExampleService that retrieves data from our backend. I can't figure out how to write unit tests for my application; which is structured as shown below:
ExampleService
public function retrieveMyToDoList(parameters):Promise
{
var promise:Promise = performRequest({request: "call to backend", parameters: values, session_id: clientModel.sessionID});
promise.addResultProcessor(parseRetrieveToDoListResult);
return promise;
}
protected function parseRetrieveToDoListResult(data:Object, callback:Function):void
{
does some JSON parsing into an object
callback(null, object containing my retrieved data)
}
ExampleModel
public function getMyToDoList():Promise
{
var promise:Promise = exampleService.retrieveToDoList(parameters);
promise.addResultHandler(onGetToDoListResult);
promise.addErrorHandler(onGetToDoListError);
return promise;
}
private function onGetHeadrsByUserResult(promise:Promise):void
{
// where this event will be listened to by mediators etc
dispatchEvent(new ResponseEvent(GOOD_RESULT));
}
private function onGetHeadrsByUserError(promise:Promise):void
{
dispatchEvent(new ResponseEvent(BAD_RESULT));
}
I'm trying to use asmock to mock my Service so that I can test my Model and how it handles the various results in the resulting Object but how do I mock the callback? I saw examples where the return values were mocked but in my case I'm using the Promise and callback and I'm not too sure how to go ahead.
If someone could please advise.
Thanks!
You can let the mock service return a real promise and call the handleResult method of the promise directly.
FYI: it's not a good idea to have a direct dependency from the model to the service. You should let the service manipulate the model, or pass the results from the service to a command which will manipulate the model. Models should never depend on anything else than helper classes.