lonesomewhistle
lonesomewhistle

Reputation: 816

How To Unit Test an @IBAction with asyncronous call

In the following code, I want to test whether "DisplayHelper.displayAlert" has been called. I am dependency-injecting DisplayHelper and AuthService mock objects, and using PromiseKit

In My ViewController:

@IBAction func submitButtonPressed(sender: AnyObject) {
    AuthService.updateUser(["zipcode": self.zipcodeTextField.text!])
        .then { user -> Void in
             // sucess code
        }.error { error -> Void in
            self.DisplayHelper.displayAlert("Zipcode Not Found", message: "We can't find that zipcode... please try again.", callingViewController: self)
        }
}

Here are the not-working tests:

func testSubmitButtonServerCantFindZipcode() {
    vc.zipcodeTextField.text = "00000"
    vc.submitButtonPressed(self)

    // called AuthService    (passes)
    XCTAssertEqual(authServiceMock.updateUserCalled, true)

    // called displayAlert (fails because tests run before DisplayAlert is called)
    XCTAssertEqual(displayHelperMock.displayAlertCalled, true)
}

How do I get the tests to wait for all of the code to execute before the assertions?

Upvotes: 2

Views: 554

Answers (1)

mokagio
mokagio

Reputation: 17471

When testing asynchronous code with XCTest you need to use XCTestExpectation.

You can rewrite the test code like this:

let e = expectationWithDescription("display alert")
waitForExpectationsWithTimeout(3) { error in
    XCTAssertEqual(displayHelperMock.displayAlertCalled, true)
}

Now the only missing piece to make the test work is finding a place where to call expectation.fulfill(). The most appropriate place would be in your AuthService mock, after the success and failure callbacks are run.

If I can suggest you something though, writing tests that asser whether certain methods have been called is not a safe approach to testing, as you are just testing implementation rather than behaviour.

What could be a better approach is to test the two componets independently. TestAuthService making sure that both the success and failure path execute as expected, and the DisplayHelper to make sure that the alert view is actually added to the view hierarchy.

This article could be a useful place to start to find out how unit test alerts, and this post is a good read on why and how to avoid mocks.

Upvotes: 2

Related Questions