ronatory ronatory - 1 month ago 15
Swift Question

XCTest Unit Test data response not set in test after viewDidLoad

I'm trying to write Unit Tests and currently I try to test a viewController which received data in

viewDidLoad()
, the data is set after a alamofire request. The problem is, that in my test function when I check the array which should be filled with 10 elements after the successful request, is 0. I've checked if
viewDidLoad()
in the test is not executed, but it should be, because when I just add elements to another array, which is outside the request, the specific test works. I guess, it has something to do with the request and I didn't found answer so far.

Here is the code (this question with explanation, helped me to execute the viewDidLoad() of the viewController):

ViewController simplified:

class ViewController: UIViewController {

var itemsFromRequest: [Int] = []
var itemsWithoutRequest: [Int] = []

override func viewDidLoad() {
super.viewDidLoad()

// array with 10 elements
itemsWithoutRequest = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Alamofire.request(url: URLConvertible).responseJSON { response in
//...error handling, parsing etc.

// set array after successful response
self.itemsFromRequest = responseData
}
}
}


The ViewControllerTests class:

class ViewControllerTests: XCTestCase {

var viewController: ViewController!

override func setUp() {
super.setUp()

let storyboard = UIStoryboard(name: "Main", bundle: nil)
viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
// trigger viewDidLoad() of viewController
let _ = viewController.view
}

func testItemArraysHaveTenItems() {
// This is successful
XCTAssertEqual(viewController.itemsWithoutRequest.count, 10)
// This failed with error "XCTAssertEqual failed: ("0") is not equal to ("10")"
XCTAssertEqual(viewController.itemsFromRequest.count, 10)
}
}


Why does the second Assertion failed?

Answer

Problem

On the second Assertion you expect data which are loaded asynchronously, but you check them synchronously and at the time you are checking for them, the network operation has not finished yet, so of course it is going to fail.

Solution

Testing asynchronously is available on iOS using XCTestExpectation.

Example of use

let testExpectation = expectation(description: "Asynchronous expectation")
let sut = SubjectUnderTest()

sut.executeAsynchronousOperation(completion: { (error, data) in
    XCTAssertTrue(error == nil)
    XCTAssertTrue(data != nil)

    testExpectation.fulfill()
})

waitForExpectations(timeout: 5, handler: .none)

Tips

Another thing to point out is that testing network operations and waiting for the response is a bad practice, you should intercept the network requests and return local data instead of waiting for the real data from the server. (On GitHub there is a nice framework to do that in Swift: OHHTTPStubs)