Using XCTest, how can one chain together multiple discrete sequences of { expectations -> wait }? - xctest

The documentation for XCTest waitForExpectationsWithTimeout:handler:, states that
Only one -waitForExpectationsWithTimeout:handler: can be active at any given time, but multiple discrete sequences of { expectations -> wait } can be chained together.
However, I have no idea how to implement this, nor can I find any examples. I'm working on a class that first needs to find all available serial ports, pick the correct port and then connect to the device attached to that port. So, I'm working with at least two expectations, XCTestExpectation *expectationAllAvailablePorts and *expectationConnectedToDevice. How would I chain those two?

I do the following and it works.
expectation = [self expectationWithDescription:#"Testing Async Method Works!"];
[AsynClass method:parameter callbackFunction:^(BOOL callbackStatus, NSMutableArray* array) {
[expectation fulfil];
// whatever
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(#"Expectation Failed with error: %#", error);
}
NSLog(#"expectation wait until handler finished ");
}];
// do it again
expectation = [self expectationWithDescription:#"Testing Async Method Works!"];
[CallBackClass method:parameter callbackFunction:^(BOOL callbackStatus, NSMutableArray* array) {
[expectation fulfil];
// whatever
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
if (error) {
XCTFail(#"Expectation Failed with error: %#", error);
}
NSLog(#"expectation wait until handler finished ");
}];

swift
let expectation1 = //your definition
let expectation2 = //your definition
let result = XCTWaiter().wait(for: [expectation1, expectation2], timeout: 10, enforceOrder: true)
if result == .completed {
//all expectations completed in order
}

Assigning my expectation to a weak variable worked for me.

This seems to be working for me in Swift 3.0 as well.
let spyDelegate = SpyDelegate()
var asyncExpectation = expectation(description: "firstExpectation")
spyDelegate.asyncExpectation = asyncExpectation
let testee = MyClassToTest(delegate: spyDelegate)
testee.myFunction() //asyncExpectation.fulfill() happens here, implemented in SpyDelegate
waitForExpectations(timeout: 30.0) { (error: Error?) in
if let error = error {
XCTFail("error: \(error)")
}
}
asyncExpectation = expectation(description: "secoundExpectation")
spyDelegate.asyncExpectation = asyncExpectation
testee.delegate = spyDelegate
testee.myOtherFunction() //asyncExpectation.fulfill() happens here, implemented in SpyDelegate
waitForExpectations(timeout: 30.0) { (error: Error?) in
if let error = error {
XCTFail("error: \(error)")
}
}

Within a class that extends XCTestCase you can use wait(for:timeout:) like this:
let expectation1 = self.expectation(description: "expectation 1")
let expectation2 = self.expectation(description: "expectation 2")
let expectation3 = self.expectation(description: "expectation 3")
let expectation4 = self.expectation(description: "expectation 4")
// ...
// Do some asyc stuff, call expectation.fulfill() on each of the above expectations.
// ...
wait(for:[expectation1,expectation2,expectation3,expectation4], timeout: 8)

Related

Converting objective-c block to Swift closure

I am trying to convert this Objective-C block into Swift:
[self.client downloadEntity:#"Students" withParams: nil success:^(id response) {
// execute code
}
failure:^(NSError *error) {
// Execute code
}];
This is my code in Swift, but the syntax seems to be a bit off:
client.downloadEntity("Students", withParams: nil, success: {(students: [AnyObject]!) -> Void in
print("here")
}, failure: { (error: NSError!) -> Void! in
print ("here")
}
This is giving me a few compilation errors:
Value of 'AnyObject' has no member 'downloadEntity'
It is complaining about the lack of commas (,) right after the failure part of the code
Try this:
client.downloadEntity("Student", withParams: nil,
success: { (responseObj) -> Void in
print("success: \(responseObj)")
},
failure: { (errorObj) -> Void in
print("treat here (in this block) the error! error:\(errorObj)")
})
You need to switch to the new Swift error syntax, and you can also using trailing closures. I had to use a bool for the example to show how you would call your success closure, or you would throw an error.
var wasSuccessful = true // This is just here so this compiles and runs
// This is a custom error type. If you are using something that throws an
// NSError, you don't need this.
enum Error:ErrorType {
case DownloadFailed
}
// Hopefully you have control over this method and you can update
// the signature and body to something similar to this:
func downloadEntity(entityName: String, success: ([AnyObject]) -> Void) throws {
let students = [AnyObject]()
// download your entity
if wasSuccessful {
// Call your success completion handler
success(students)
}
else {
throw Error.DownloadFailed
}
}
When you have a function that can throw an error, you need to call it with try inside a do/catch block.
// Calling a function that can throw
do {
try downloadEntity("Students") { students in
print("Download Succeded")
}
}
catch Error.DownloadFailed {
print("Download Failed")
}
// If you are handling NSError use this block instead of the one above
// catch let error as NSError {
// print(error.description)
// }

Load PageViewController After Asynchronous Task Complete

I'm having trouble loading the PageViewController after the async call is complete. I was considering using NSNotification, but not sure what is the best approach.
Async func to fetch images
func fetchArrayImages() {
var query = PFQuery(className: "FoodPhoto")
query.orderByDescending("Votes")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let photoUploaded = object["PhotoUploaded"] as PFFile
photoUploaded.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
//image object implementation
self.photosUploadedArray.append(image!)
}
})
}
}
else{
println("Error in retrieving \(error)")
}
})
}
Func to be called after async download images
This loads the UIPageViewController
func loadPhotosFromArray() {
var array = photosUploadedArray
view1 = PhotoCollevtionView(outerFrame: self.view.frame, photoArray: array, currentNumber: 0)
self.view.addSubview(view1!)
}
You can check your uploaded image is equals with last image in your imageObjects array and in this case you can call your loadPhotosFromArray() code like this:
self.photosUploadedArray.append(image!)
if ( imageObjects.last.isEqual(image!)) { //Use this code
loadPhotosFromArray()
}

Hide Activity Indicator Does Not Work

The activity indicator starts, but does not stop when the hide function is called. I've tried putting the hide function in various places, and it still does not hide.
Hide activity indicator: Q0ViewController().hideActivityIndicator(self.view)
I'm using the swift utility function found here:
https://github.com/erangaeb/dev-notes/blob/master/swift/ViewControllerUtils.swift
Start activity indicator
override func viewDidLoad() {
super.viewDidLoad()
Q0ViewController().showActivityIndicator(self.view)
self.locationManager.delegate = self //location manager start
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
Hide activity indicator after query:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if (error != nil) {
println("Error:" + error.localizedDescription)
//return
}
if placemarks.count > 0 {
let pm = placemarks[0] as CLPlacemark
self.displayLocationInfo(pm)
currentLoc = manager.location
currentLocGeoPoint = PFGeoPoint(location:currentLoc)
var query = PFQuery(className:"test10000")
query.whereKey("RestaurantLoc", nearGeoPoint:currentLocGeoPoint, withinMiles:100) //filter by miles
query.limit = 1000 //limit number of results
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if objects != nil {
unfilteredRestaurantArray = objects
originalUnfilteredArray = objects
println(objects)
} else {
println("error: \(error)")
}
Q0ViewController().hideActivityIndicator(self.view) //HIDE
}
} else {
println("error: \(error)")
}
})
}
It is not an issue with the main queue as dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), { ()->() in does not resolve the issue.
Looks like you're creating a new instance of the "Q0ViewController" each time.
Instead I would suggest retaining the initial instance as a property on your class:
// As a variable on the class instance
let myViewController = Q0ViewController()
// Initially show the activity indicator
self.myViewController.showActivityIndicator(self.view)
// Hide the activity indicator
self.myViewController.hideActivityIndicator(self.view)
Hopefully this helps!
Similar to what Joshua suggested, just replaced:
Q0ViewController().showActivityIndicator(self.view)
and
Q0ViewController().hideActivityIndicator(self.view)
To:
self.showActivityIndicator(self.view)
and
self.hideActivityIndicator(self.view)

Return Value from Function Swift

I know this is probably a simple queston, I would like to return the value of currentLocGeoPoint and return the array of Objects which is of type PFObject.
Tried to save it as a global variable, but it doesn't work because it is asynchronous and doesn't take a value yet. Returns empty.
Tried to return currentLocGeoPoint and changed Void in to PFGeoPoint in. Gives error: PFGeoPoint is not convertible to 'Void'
So I'm not sure how I can fetch the variable currentLocGeoPoint.
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if (error != nil) {
println("Error:" + error.localizedDescription)
//return
}
if placemarks.count > 0 {
let pm = placemarks[0] as CLPlacemark
self.displayLocationInfo(pm)
currentLoc = manager.location
currentLocGeoPoint = PFGeoPoint(location:currentLoc)
var query = PFQuery(className:"Bar")
query.whereKey("BarLocation", nearGeoPoint:currentLocGeoPoint, withinMiles:10)
query.limit = 500
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if objects != nil {
} else {
println("error: \(error)")
}
}
} else {
println("error: \(error)")
}
})
}
I don't understand the notion of "I want to return currentLocGeoPoint". Return it to what? You're in a CLLocationManagerDelegate method, so there's no one to return it to.
What you could do, though, is, when the request is done (i.e. within this closure), call some other function that needed the currentLocGeoPoint. Or you could update the UI to reflect the updated information (make sure to dispatch that update to the main thread, though). Or, if you have other view controllers or model objects that need to know about the new data, you might post a notification, letting them know that there is an updated currentLocGeoPoint. But within this method, there's no one to whom you would "return" the data.
You could assign it to a stored property of your class. Just use
self.<property> = currentLocGeoPoint

How to add a completion block to an IOS call

I need to wait for savePhototoImage to complete before moving on in my processing. I assume a completion block is the way to do this.
I have seen a few completion blocks in IOS code, but do not know much about how they are made up.
Can a completion block be added to any function and if so, what would be the correct syntax to add one to this function?
BOOL saved = [Network savePhotoImage:img :self.description :#"Photo"];
ViewController.m
[Network savePhotoImage:img :self.description :#"Photo" withCallback:^(BOOL success)
{
NSLog(#"executing callback");
if (success)
{
NSLog(#"got callback success");
}
else
{
NSLog(#"got callback failure");
}
}];
Network.m
+ (void)savePhotoImage:(UIImage*)PhotoImage :(NSString*)description :(NSString*)imageName withCallback:(ASCompletionBlock)callback
{
add workdone code here...
if (workdone)
callback(YES);
} else {
callback(NO);
}
}
Network.h
typedef void (^ASCompletionBlock)(BOOL success);
+ (void)savePhotoImage:(UIImage*)PhotoImage :(NSString*)description : (NSString*)imageName withCallback:(ASCompletionBlock)callback;