Writing Unit Test with asynchronous code in IOS - ios

I need to test my Api calls response but before the block return Unit test got completed
how can i test my APIs

You can use XCTestExpectation for that.
XCTestExpectation *apiCallExpectation = [self expectationWithDescription:#"APICall"];
[apiService apiCall:^(BOOL success) {
XCTAssert(success);
[apiCallExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
[apiCallExpectation closeWithCompletionHandler:nil];
}];

At first, you don't need to call the actual API in unit testing, It shall to be independent and quicker to be completed. This shall be part of integration testing.
Regarding your question, I think you need to use Expectations, and Waiter. Check the following:
func testExample() {
let responseExpectation = expectation(description: "response")
// Your API Call shall be here
DispatchQueue.main.async {
// When you get the response, and want to finalize the expectation
responseExpectation.fulfill()
}
let result = XCTWaiter.wait(for: [responseExpectation], timeout: 15) // ex: 15 seconds to wait for the response for all expectations.
// result possible values are:
//all expectations were fulfilled before timeout.
.completed
//timed out before all of its expectations were fulfilled
.timedOut
//expectations were not fulfilled in the required order
.incorrectOrder
//an inverted expectation was fulfilled
.invertedFulfillment
//waiter was interrupted before completed or timedOut
.interrupted
}

Following is an example i am performing test case on a search api. you need to declare expectation and it will get fulfil once it's done.
func test_UpdateShowSearch_Result() {
let promise = expectation(description: "Status code: 200")
let searchAPI: SearchShowApi = SearchShowApi()
searchAPI.search(query: "") { (statusCode, tvShows ,error) in
if statusCode == 200 {
// reload table
promise.fulfill()
} else if (statusCode == 204){
// show no content
XCTFail("Status code: \(statusCode)")
}
else{
XCTFail("Error: \(String(describing:error?.localizedDescription))")
return
}
}
wait(for: [promise], timeout: 10)
}

Related

Serial DispatchQueue is not working as expected

To load some information in my app's view, I need it to finish networking because some methods depend on the result. I looked into serial DispatchQueue and .async methods, but it's not working as expected.
Here is what I tried so far. I defined 3 blocks:
Where I'd get hold of the user's email, if any
The email would be used as input for a method called getData, which reads the database based on user's email address
This block would populate the table view with the data from the database. I've laid it out like this, but I'm getting an error which tells me the second block still executes before we have access to user's email address, i.e. the first block is finished. Any help is appreciated, thanks in advance!
let serialQueue = DispatchQueue(label: "com.queue.serial")
let block1 = DispatchWorkItem {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
print("unable to identify user")
} else {
print(user!.profile?.email ?? "")
self.email = user!.profile?.email ?? ""
print("email is: \(self.email)")
}
}
}
let block2 = DispatchWorkItem{
self.getData(self.email)
}
let block3 = DispatchWorkItem {
DispatchQueue.main.async {
self.todoListTable.reloadData()
}
}
serialQueue.async(execute: block1)
block1.notify(queue: serialQueue, execute: block2)
block2.notify(queue: serialQueue, execute: block3)
Your problem is that you are dispatching asynchronous work inside your work items; GIDSignIn.sharedInstance.restorePreviousSignIn "returns" immediately but fires the completion handler later when the work is actually done. Since it has returned, the dispatch queue considers the work item complete and so moves on to the next item.
The traditional approach is to invoke the second operation from the completion handler of the first (and the third from the second).
Assuming you modified self.getData() to have a completion handler, it would look something like this:
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
print("unable to identify user")
} else {
print(user!.profile?.email ?? "")
self.email = user!.profile?.email ?? ""
print("email is: \(self.email)")
self.getData(self.email) {
DispatchQueue.main.async {
self.todoListTable.reloadData()
}
}
}
}
You can see you quickly end up with a "pyramid of doom".
The modern way is to use async/await. This requires your functions to support this approach and imposes a minimum iOS level of iOS 13.
It would look something like
do {
let user = try await GIDSignIn.sharedInstance.restorePreviousSignIn()
if let email = user.profile?.email {
let fetchedData = try await getData(email)
self.data = fetchedData
self.tableView.reloadData()
}
} catch {
print("There was an error \(error)")
}
Ahh. Much simpler to read.

GKTurnBasedMatch saveCurrentTurnWithMatchData returning an error on every other call

The player takes multiple actions before completing a turn. After each action, I call saveCurrentTurnWIthMatchData, with the match data updated.
[gameMatch saveCurrentTurnWithMatchData: matchData completionHandler: ^(NSError *error){
if (error) {
NSLog(#"Error updating match = %#",error);
}
}];
On every other call I get "Error Domain=GKServerErrorDomain Code=5002 "status = 5002, Unexpected game state version expectedGameStateVersion='null'"
The GKTurnBasedMatch.state = 3 (GKTurnBasedMatchStatusMatching) in every call. I'm not changing this, I just check before the call. I have no idea if this is relevant.
Any suggestion what to try?
the "Unexpected game state version" error happens irregularly and is hard to reproduce -- although i can often reproduce it by calling saveCurrentTurn several times in rapid succession. it would be useful to have clarity from Apple on this since it appears to be server side (but i'm not sure). i wrote a unit test that does stress testing on GKTurnBasedMatch.saveCurrentTurn. it fails irregularly but often up to 20% of the time.
i have no full solution only a partial one. to partially mitigate the problem, you can wrap your saveCurrentTurn calls in a task queue, that way they wait for the previous one to finish. not a solution, but helps.
let dqt:DispatchQueueTask = {
gkTurnBasedMatch.saveCurrentTurn(withMatch:payload) { error in
//handle error
TaskQueue.completion() //step to next task
}
}
TaskQueue.add(task:dqt)
and here is the TaskQueue class i use
import Foundation
/*
Uses the DispatchQueue to execute network commands in series
useful for server commands like GKTurnBasedMatch.saveCurrentTurn(...)
Usage:
let doSomethingThatTakesTime:DispatchQueueTask = {
...
TaskQueue.completion()
}
TaskQueue.add(task: doSomethingThatTakesTime)
*/
typealias DispatchQueueTask = () -> ()
let DispatchQueue_serial = DispatchQueue(label: "org.my.queue.serial")
class TaskQueue {
static var isRunning:Bool = false
static var tasks:[DispatchQueueTask] = []
static func add(task:#escaping DispatchQueueTask) {
tasks.append(task)
run()
}
static func run() {
guard !isRunning else { return }
guard tasks.count > 0 else { return }
let task = tasks.removeFirst()
DispatchQueue_serial.async {
TaskQueue.isRunning = true
task()
}
}
static func completion() {
TaskQueue.isRunning = false
TaskQueue.run()
}
}

What Happens if Fulfill is called Twice on a Promise

This code is blocking the UI until the remote config fetch is done. I have put 2 seconds as timeout. Here both completionHandler and after will execute, whichever finishes first.
Will it be ok? Or do I need to take care that only one should execute?
func checkIfRemoteConfigFetched<T>(t:T) -> Promise<T>{
return Promise<T>{ seal in
if self.rConfig?.fetchComplete == true{
seal.fulfill(t)
}
after(seconds: 2).done{
seal.fulfill(t)
}
if let rConfig = self.rConfig{
rConfig.completionHandler = { success in
seal.fulfill(t)
}
}
}
}

Performing an asynchronous task within a synchronous call

I would like to register a user which is performed asynchronous. However, the calling function behaves synchronous since the program should only continue when a user is created successfully.
The current implementation is:
class SignUp: NSObject {
// ...
func signUpUser() throws -> Bool {
guard hasEmptyFields() else {
throw CustomErrorCodes.EmptyField
}
guard isValidEmail() else {
throw CustomErrorCodes.InvalidEmail
}
createUser( { (result) in
guard result else {
throw CustomErrorCodes.UserNameTaken
}
return true // Error: cannot throw....
})
}
func createUser( succeeded: (result: Bool) -> () ) -> Void {
let newUser = User()
newUser.username = username!
newUser.password = password!
// User is created asynchronously
createUserInBackground(newUser, onCompletion: {(succeed, error) -> Void in
if (error != nil) {
// Show error alert
} else {
succeeded(result: succeed)
}
})
}
}
and in a ViewController the signup is initiated as follows:
do {
try signup.signUpUser()
} catch let error as CustomErrorCodes {
// Process error
}
However, this does not work since createUser is not a throwing function. How could I ensure that signUpUser() only returns true when an new user is created successfully?
You say:
and in a ViewController the signup is initiated as follows:
do {
try signup.signUpUser()
} catch let error as CustomErrorCodes {
// Process error
}
But don't. That's not how asynchronous works. The whole idea is that you do not wait. If you're waiting, it's not asynchronous. That means you're blocking, and that's just what you mustn't do.
Instead, arrange to be called back at the end of your asynchronous process. That's when you'll hear that things have succeeded or not. Look at how a download task delegate is structured:
https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionDownloadTask_class/
The download task calls back into the delegate to let it know whether we completed successfully or not. That is the relationship you want to have with your asynchronous task. You want to be like that delegate.
You need to adjust your thinking. Instead of trying to write a synchronous method that we need to wait for an asynchronous event, write a method that takes a completion closure. The method will return immediately, but once the asynchronous process is complete it wild invoke the completion closure. When you call such a method you pass in code in the incompletion closure that gets called once the job is done.

completionHandler connot be executed in unit test of Xcode, ex. asynchronous api - reverseGeocodeLocation of CLGeocoder

I understand CLGeocoder().reverseGeocodeLocation(...) will be executed in another thread, so that completionHandler will be executed in the main thread once it successfully gets the result from Apple server. The problem is I cannot see what happened in the completionHandler when I'm using in unit tests(when I build the framework), but it works fine in the Application. For simplify my question, I extract the asynchronous part from my codes in unit test as follows:
func testGeo() {
var location = CLLocation(latitude: 45.0, longitude: 135.5)
var semaphore = dispatch_semaphore_create(0)
println("------fetch----->")
CLGeocoder().reverseGeocodeLocation(location, completionHandler:{ (placemarks, error) -> Void in
if error != nil {
println("[ERROR]: \(error) in getPlacemarkFromLocation")
}
if placemarks.count > 0 {
println("[SUCCESS] get the placemark!")
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
} else {
println("[ERROR] get 0 placemarks in getPlacemarkFromLocation")
}
dispatch_semaphore_signal(semaphore)
})
println("------wait--- -->")
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
When I do this unit test, it seems to not execute the completionHandler because nothing to print in the console. Every time I use asynchronous api, I cannot get the return value successfully in my unit test. I need to use unit test to make sure all the pipe of my framework works well. This gonna make me crazy... How can I fix this problem? Thanks a lot :)
The problem is because the test functions exit before the completion handler finishes.
From Xcode 6, Apple shipped an asynchronous testing facility called XCTestExpectation. This thing enables us to wait some time before going out of scope. Usage is as simple as:
let expect = self.expectationWithDescription("exp")
sc.asyncCall(para) { (data, error) in
XCTAssertNotNil(data)
expect.fulfill()
}
self.waitForExpectationsWithTimeout(1) { (error) in
guard error == nil else {
XCTAssert(false)
NSLog("Timeout Error.")
return
}
}

Resources