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 XCTExpectation
s.
Related Topics
Swift Can't Infer Generic Type When Generic Type Is Being Passed Through a Parameter
Uipickerview Selectrow Doesn't Works
Swiftui Coordinator Not Updating the Containing View's Property
How to Subclass Nsoperation in Swift to Queue Skaction Objects for Serial Execution
Nsjsonserialization Not Working as Expected in a Playground
How to Distinguish Between Multiple Uipickerviews on One Page
How to Click a Button Programmatically
How to Save Document to "Files" App in Swift
Repeating Animation on Swiftui Image
Redeclaring Members in an Extension Hides the Original Member *Sometimes*. Why
Safe to Signal Semaphore Before Deinitialization Just in Case
Flip Arfaceanchor from Left-Handed to Right-Handed Coordinate System
Swift: Using "/" Slash in Filename with Createdirectoryatpath
Index Out of Range in Swift with Removeatindex