With Xcode 7 Apple introduced a new framework for UI testing. As with every new thing there is always room for improvement, and with Xcode 7 Beta 4, UI testing got a very useful improvement: the ability to wait asynchronously for view changes.
Xcode 7 beta 4 adds async waiting for UI changes in UI testing with:
-[XCTestCase expectationForPredicate:evaluatedWithObject:handler:]
— Joar Wingfors (@joar_at_work) July 22, 2015
Joar's tweet is enlightening but doesn't show any code, so let's see how to do that. You can checkout the example project as well if you want.
func expectationForPredicate(predicate: NSPredicate, evaluatedWithObject object: AnyObject, handler: XCPredicateExpectationHandler?) -> XCTestExpectation
The header for this method reads: creates an expectation that is fulfilled if the predicate returns true when evaluated with the given object. The expectation periodically evaluates the predicate and also may use notifications or other events to optimistically re-evaluate. If the handler is not provided the first successful evaluation will fulfill the expectation. If provided, the handler can override that behavior which leaves the caller responsible for fulfilling the expectation.
So what we need to do to assert the changes in our views is write a test predicate and pass it to expectationForPredicate
, together with the object the predicate has to be evaluated with, and an optional handler.
More in details here's what Joar suggests:
@mokagio That looks alright. Usually something makes the element change state, and that something should go after creating the expectation.
— Joar Wingfors (@joar_at_work) July 24, 2015
Let's take a sample app in which we have a button that once touched triggers an delayed update in a label. This is somehow similar to an "Updated at..." label that updates its text once the app receives the response to an update request to the server. A possible UI test for it would be:
func testOtherButtonChangesFooter() {
let app = XCUIApplication()
// Define the expectation on the final UI state
//
let expectedText = "Oh! Did something happen?!"
let labelIdentifier = "footer label"
let testPredicate = NSPredicate(format: "label = '\(expectedText)'")
let object = app.staticTexts.elementMatchingType(.Any, identifier: labelIdentifier)
self.expectationForPredicate(testPredicate, evaluatedWithObject: object, handler: nil)
// Act on the UI to change the state
//
app.buttons["Press me and I'll do something, eventually"].tap()
// Wait and see...
//
self.waitForExpectationsWithTimeout(10, handler: nil)
}
That's it. I wouldn't go so far as saying as simple as that, because there is a bit of setup to do in order to run the assertion.
If you would like to see more examples of this kind of test, or would like to share your implementations please leave a comment below, or tweet me @mokacoding.
Leave the codebase better than you found it.