How to Mock Network Requests in Xcode UI Tests While The Tests Are Running

Is it possible to stub HTTP requests in Xcode 7 automated UI tests?

As Martijn correctly pointed out, because of how UI tests work, you cannot directly interact with the app at runtime, so any HTTP mocking or manipulation of things like NSUserDefaults in a XCUITestCase will not affect your app.

If you really need to be able to mock HTTP or setup & teardown your apps environment for specific UI tests, you will need to set launch arguments or launch environment variables before launching the app in the setUp() method of a XCUITestCase and then modify your app code to read the launch arguments or environment variables and bootstrap the test environment.

Example TestCase

class MyTestCase: XCTestCase {

/**
Called before each test in this test case.
*/
override func setUp() {
super.setUp()

let app = XCUIApplication()
app.launchArguments = [ "STUB_HTTP_ENDPOINTS" ]
app.launch()
}

}

Example AppDelegate

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

#if DEBUG
if (NSProcessInfo.processInfo().arguments.contains("STUB_HTTP_ENDPOINTS")) {
// setup HTTP stubs for tests
}
#endif

return true
}

Note: In order to use an HTTP mocking framework like OHHTTPStubs in this example, the stubbing code and any JSON fixtures you need to use will all need to be in your app target, not the test target.

This is a very useful thread to read on the topic: https://github.com/AliSoftware/OHHTTPStubs/issues/124

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)
}
}
}

How to mock data in UITest on Xcode 7?

I think there are a lot of ways to approach this one - the difficulty is that Apple has intentionally designed UITests to run entirely separate from the app under test. That said, there are a few hooks you can use to coordinate logic in the app with logic in your tests to feed in mock data or alter the behavior of your app in any way. The two I have found most useful are launchEnvironment and launchArguments.

in your test - XCUIApplication().launchArguments corresponds to NSProcessInfo.processInfo().arguments in your app code

likewise:
XCUIApplication().launchEnvironment -> NSProcessInfo.processInfo().environment

launchEnvironment is a straight forward dictionary whereas launch arguments is an array. In your test you can feed any values you like into either of these parameters before you launch the app:

let app = XCUIApplication()
app.launchEnvironment["-FakedFeedResponse"] = "success.json"
app.launch()

Then in your application logic you can switch on these values however you like. Something like:

func fetchFeed() -> JSON {
if let fakedJSONFilename = NSProcessInfo.processInfo().environment["-FakedFeedResponse"] {
let fakePayload = fakeDataFileNamed(fakedJSONFilename)
return fakePayload
} else {
//Make network call and return a real JSON payload
}
}

Using this pattern your faked/mock data will need to be files included as members of the app target.

Is it possible to stub HTTP requests in Xcode 7 automated UI tests?

As Martijn correctly pointed out, because of how UI tests work, you cannot directly interact with the app at runtime, so any HTTP mocking or manipulation of things like NSUserDefaults in a XCUITestCase will not affect your app.

If you really need to be able to mock HTTP or setup & teardown your apps environment for specific UI tests, you will need to set launch arguments or launch environment variables before launching the app in the setUp() method of a XCUITestCase and then modify your app code to read the launch arguments or environment variables and bootstrap the test environment.

Example TestCase

class MyTestCase: XCTestCase {

/**
Called before each test in this test case.
*/
override func setUp() {
super.setUp()

let app = XCUIApplication()
app.launchArguments = [ "STUB_HTTP_ENDPOINTS" ]
app.launch()
}

}

Example AppDelegate

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

#if DEBUG
if (NSProcessInfo.processInfo().arguments.contains("STUB_HTTP_ENDPOINTS")) {
// setup HTTP stubs for tests
}
#endif

return true
}

Note: In order to use an HTTP mocking framework like OHHTTPStubs in this example, the stubbing code and any JSON fixtures you need to use will all need to be in your app target, not the test target.

This is a very useful thread to read on the topic: https://github.com/AliSoftware/OHHTTPStubs/issues/124



Related Topics



Leave a reply



Submit