Delay/Wait in a Test Case of Xcode UI Testing

Delay/Wait in a test case of Xcode UI testing

Asynchronous UI Testing was introduced in Xcode 7 Beta 4. To wait for a label with the text "Hello, world!" to appear you can do the following:

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

More details about UI Testing can be found on my blog.

Wait 30 sec in Xcode UI Test

The simplest way is just sleeping the execution for the time being:

sleep(30)

However, in case you are expecting something to appear, it's better to use the built-in function for waiting for existence:

element.waitForExistence(30)

It doesn't fail if nothing appears, so if it's crucial part of your logic, you probably better to check it via expectation with timer:

let exists = NSPredicate(format: "exists == %@", true)
expectationForPredicate(exists, evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(30, handler: nil)

How to wait in a XCTest for T seconds without timeout error?

You can use XCTWaiter.wait functions.

For example:

let exp = expectation(description: "Test after 5 seconds")
let result = XCTWaiter.wait(for: [exp], timeout: 5.0)
if result == XCTWaiter.Result.timedOut {
XCTAssert(<test if state is correct after this delay>)
} else {
XCTFail("Delay interrupted")
}

How to wait for UI element without failing test case

You will have to reimplement waiting. As terrible as it sounds, I recommend using Sleep and If statements; you want to lock your test thread while it waits for the app to finish presenting content, then evaluate.

Unfortunately, as far as I'm aware none of the options for waitForExpectationsWithTimeout permit you to not fail the test if the expectationForPredicate is not fulfilled at the end of the timeout time.

Delay in unit test

I have two suggestions to use together:

  • Use a spy test double to make sure that the service your data store uses to refresh the clinics is called twice
  • Inject the refresh interval to make the tests faster

Spy test double

Testing the side effect of the data loading, that it hits the service, could be a way to simplify your test.

Instead of using different expectations and exercising the system under test in a way that might not be what happens at runtime (the dataStore.clinicsSignal.fire([])) you can just count how many times the service is hit, and assert the value is 2.

Inject refresh interval

The approach I would recommend is to inject the time setting for how frequently the clinics should be updated in the class, and then set a low value in the tests.

After all, I'm guessing what you are interested in is that the update code runs as expected, not every 10 seconds. That is, it should update at the frequency you set.

You could do this by having the value as a default in the init of your data store, and then override it in the tests.

The reason I'm suggesting to use a shorter refresh interval is that in the context of unit testing, the faster they run the better it is. You want the feedback loop to be as fast as possible.

Putting it all together, something more or less like this

protocol ClinicsService {
func loadClinics() -> SignalProducer<[Clinics], ClinicsError>
}

class DataSource {

init(clinicsService: ClinicsService, refreshInterval: TimeInterval = 5) { ... }
}

// in the tests

class ClinicsServiceSpy: ClinicsService {

private(var) callsCount: Int = 0

func loadClinics() -> SignalProducer<[Clinics], ClinicsError> {
callsCount += 1
// return some fake data
}
}

func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)

// This is an async expectation to make sure the call count is the one you expect
_ = expectation(
for: NSPredicate(
block: { input, _ -> Bool in
guard let spy = input as? ClinicsServiceSpy else { return false }
return spy.callsCount == 2
),
evaluatedWith: clinicsServiceSpy,
handler: .none
)

dataStore.loadData()

waitForExpectations(timeout: .2, handler: nil)
}

If you also used Nimble to have a more refined expectation API your test could look like this:

func testRefresh() {
let clinicsServiceSpy = ClinicsServiceSpy()
let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)

dataStore.loadData()

expect(clinicsServiceSpy.callsCount).toEventually(equal(2))
}

The tradeoff you make in this approach is to make the test more straightforward by writing a bit more code. Whether it's a good tradeoff is up to you do decide.

I like working in this way because it keeps each component in my system free from implicit dependencies and the test I end up writing are easy to read and work as a living documentation for the software.

Let me know what you think.

Swift2 UI Test - Wait for Element to Appear

In expectationForPredicate(predicate: evaluatedWithObject: handler:) you don't give an actual object, but a query to find it in the view hierarchy. So, for example, this is a valid test:

let predicate = NSPredicate(format: "exists == 1")
let query = XCUIApplication().buttons["Button"]
expectationForPredicate(predicate, evaluatedWithObject: query, handler: nil)

waitForExpectationsWithTimeout(3, handler: nil)

Check out UI Testing Cheat Sheet and documentation generated from the headers (there is no official documentation at the moment), all by Joe Masilotti.

Wait for all HTTP requests to finish in XCode UI tests?

Your best bet is to wait for some UI element to appear or disappear. Think of it this way:

The framework acts like a user. It doesn't care what code is running under the hood. The only thing that matters is what is visible on the screen.

That said, here is how you can wait for a label titled "Go!" to appear in your UI Tests.

let app = XCUIApplication()
let goLabel = self.app.staticTexts["Go!"]
XCTAssertFalse(goLabel.exists)

let exists = NSPredicate(format: "exists == true")
expectationForPredicate(exists, evaluatedWithObject: goLabel, handler: nil)

app.buttons["Ready, set..."].tap()
waitForExpectationsWithTimeout(5, handler: nil)
XCTAssert(goLabel.exists)

You could also extract that into a helper method. If you use some Swift compiler magic you can even get the failure message to occur on the line that called the method.

private fund waitForElementToAppear(element: XCUIElement, file: String = #file, line: UInt = #line) {
let existsPredicate = NSPredicate(format: "exists == true")
expectationForPredicate(existsPredicate, evaluatedWithObject: element, handler: nil)

waitForExpectationsWithTimeout(5) { (error) -> Void in
if (error != nil) {
let message = "Failed to find \(element) after 5 seconds."
self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
}
}
}

Wait for NSNotification before continuing

Effective UI Tests are written with a different mindset than unit tests and production code. UI Tests should be written from the user's perspective and not care what goes on "under the hood" of the app. For example, your test should wait for a specific UI element to appear instead of waiting for an NSNotification to fire or a specific network request to complete.

Wait for An Element to Appear

Let's say your first tab/screen displays user information. Contained in that data is the user's username. You can use XCTest's asynchronous testing API to pause the framework until the element is found (or a timeout occurs).

let app = XCUIApplication()
let username = self.app.staticTexts["joemasilotti"]
let exists = NSPredicate(format: "exists == true")
expectationForPredicate(exists, evaluatedWithObject: username, handler: nil)

waitForExpectationsWithTimeout(5, handler: nil)
XCTAssert(username.exists)

This comes directly from a UI Testing cheat sheet I put together. It also has a working example app that you can run in Xcode and play around with the code and tests.



Related Topics



Leave a reply



Submit