Waiting for Alamofire in Unit Tests

Waiting for Alamofire in Unit Tests

Use XCTestExpectation to wait for asynchronous processes, for example:

func testExample() {
let e = expectation(description: "Alamofire")

Alamofire.request(urlString)
.response { response in
XCTAssertNil(response.error, "Whoops, error \(response.error!.localizedDescription)")

XCTAssertNotNil(response, "No response")
XCTAssertEqual(response.response?.statusCode ?? 0, 200, "Status code not 200")

e.fulfill()
}

waitForExpectations(timeout: 5.0, handler: nil)
}

In your case, if you're going to test your asynchronous method, you have to provide a completion handler to refreshThingy:

class Thingy {

var property: String!

func refreshThingy(completionHandler: ((String?) -> Void)?) {
Alamofire.request(someURL)
.responseJSON { response in
if let json = response.result.value as? [String: String] {
completionHandler?(json["JSON"])
} else {
completionHandler?(nil)
}
}
}
}

Then you can test Thingy:

func testThingy() {
let e = expectation(description: "Thingy")

let thingy = Thingy()
thingy.refreshThingy { string in
XCTAssertNotNil(string, "Expected non-nil string")
e.fulfill()
}

waitForExpectations(timeout: 5.0, handler: nil)
}

Frankly, this pattern of using a completion handler is probably something that you want in your refreshThingy, anyway, but I made it optional in case you might not want to supply a completion handler.

How to wait until get the response from component under test that use Alamofire? - Xcode

Hi @Raghad ak, welcome to Stack Overflow .

Your guess about the passage of time preventing the test to succeed is correct.

Networking code is asynchronous. After the test calls .sendActions(for: .touchUpInside) on your login button it moves to the next line, without giving the callback a chance to run.

Like @ajeferson's answer suggests, in the long run I'd recommend placing your Alamofire calls behind a service class or just a protocol, so that you can replace them with a double in the tests.

Unless you are writing integration tests in which you'd be testing the behaviour of your system in the real world, hitting the network can do you more harm than good. This post goes more into details about why that's the case.

Having said all that, here's a quick way to get your test to pass. Basically, you need to find a way to have the test wait for your asynchronous code to complete, and you can do it with a refined asynchronous expectation.

In your test you can do this:

expectation(
for: NSPredicate(
block: { input, _ -> Bool in
guard let label = input as? UILabel else { return false }
return label.text == "logged in successfully"
}
),
evaluatedWith: controllerUnderTest.lblValidationMessage,
handler: .none
)

controllerUnderTest.loginButton?.sendActions(for: .touchUpInside)

waitForExpectations(timeout: 10, handler: nil)

That expectation will run the NSPredicate on a loop, and fulfill only when the predicate returns true.

Need to wait for Alamofire request to complete before proceeding - Swift

Basically, you will need to move the code after the Alamofire request into the request closure. The code inside the request closure will only fire after the request completes successfully or fails (server error, timeout, etc.) The following code should work for you.



@IBAction func btnScanPressed(_ sender: Any) {
// Retrieve the QRCode content
// By using the delegate pattern
readerVC.delegate = self

readerVC.completionBlock = { (result: QRCodeReaderResult?) in
print(result?.value)

Alamofire.request((result?.value)!).responseString { response in
debugPrint(response)

if let json = response.result.value {

let returnedJSON = JSON.init(parseJSON: json);


//TESTED -> this section does work and does update the values

self.co.setVideoURL(url: (returnedJSON["VideoURL"].string)!)

self.co.setDisplayText(text: returnedJSON["DisplayText"].string!)

self.co.setHasUpdated(value: true)


print("VARIABLES NOW SET")


// Presents the readerVC as modal form sheet (This needs to happen AFTER the Alamofire has completed)
self.readerVC.modalPresentationStyle = .formSheet

self.present(readerVC, animated: true, completion: nil)

}


}

}



}

Unit Testing HTTP traffic in Alamofire app

I'm adding another answer since I've just found this approach that in my opinion is easier and really simple to read and use.

I've created a dummy Alamofire class that contains only the functions and the types necessary for tests.
Now I include this file in the test target instead of the real Alamofire.

For example I've created my version of the Request class where I define a couple of static variables that I valorise depending on the test, and for this class I've implemented only the init and the responseJSON function.

public class Request {

var request:String?
struct response{
static var data:NSHTTPURLResponse?
static var json:AnyObject?
static var error:NSError?
}

init (request:String){
self.request = request
}

public func responseJSON(options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {

completionHandler(NSURLRequest(URL: NSURL(string:self.request!)!), Request.response.data, Request.response.json, Request.response.error)
return self
}
}

Now I can mock a response in a test:

func testMytestFunction(){
var HTMLResponse = NSHTTPURLResponse(URL: NSURL(string: "myurl")!, statusCode: 200, HTTPVersion: "HTTP/1.1", headerFields: nil)

Request.response.data = HTMLResponse
Request.response.json = LoadDataFromJSONFile("MyJsonFile")

request(.POST, "myurl", parameters: nil, encoding: ParameterEncoding.JSON).responseJSON {
(request, response, JSON, error) -> Void in
// the JSON and response variable now contains exactly the data that you have passed to Request.response.data and Request.response.json
}
}

The request function is defined here:

public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {

return Request(request: URLString.URLString)
}

public func request(URLRequest: URLRequestConvertible) -> Request {

return Request(request: "fakecall")
}

How to write unit test for http request with Alamofire?

Use a framework like OHHTTPStubs to stub you network requests or make real network requests. In either case, XCTest has a variety of methods for asynchronous waiting, like XCTExpectations.



Related Topics



Leave a reply



Submit