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

2

8

I've been trying to intercept and stub/mock HTTP requests in Xcode 7 automated UI tests, using tools like OHHTTPStubs, with no luck.

Here's an example of how I am trying to capture any HTTP request using OHHTTPStubs in the setUp method of a UI test file:

override func setUp() {
  super.setUp()

  let matcher: OHHTTPStubsTestBlock = { (request) -> Bool in
    return true
  }

  OHHTTPStubs.stubRequestsPassingTest(matcher) { (response) -> OHHTTPStubsResponse! in
    return OHHTTPStubsResponse.init()
  }
}

Is there something about the way that UI testing works that prevents this? has anyone been able to achieve this?

Moldy answered 28/9, 2015 at 16:22 Comment(3)
Hey did you end up with a solution?Linares
Yes, just added below.Moldy
To dynamically stub HTTP requests you could use SBTUITestTunnel, check my answer hereFlashbulb
M
10

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

Moldy answered 8/10, 2015 at 15:4 Comment(2)
It sounds like although this will indeed allow you to achieve stubbing, you will also be shipping an app with stubbing "code" and stubbed network files inside of it. Which is probably a very bad thing to be doing.Uela
I wrap the stubbing code in an if check on the DEBUG preprocessor macro, so the stubbing code should not actually make it into the App Store build. I've updated the code samples to show this. Thanks for pointing this out.Moldy
R
3

UI tests are ran in a separate instance from your application. While the classes from the application might be available to you, they are merely a copy.

In your application you can detect if you're running in UI testing mode with solutions provided here: How to detect if iOS app is running in UI Testing mode

I personally went with the launchEnvironment solution mentioned in the original post; my setUp looks like this:

override func setUp() {
    super.setUp()

    let app = XCUIApplication()
    app.launchEnvironment["TEST"] = "1"
    app.launch()
}

And one of my singleton instantiators (called RealmManager) looks like this (for instantiating a Realm database):

func realm() -> Realm {
    let dic = NSProcessInfo.processInfo().environment
    if dic["TEST"] != nil {
        return try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "test"))
    }
    return try! Realm()
}

If you dislike the duplication, but you're probably already duplicating XCUIApplication().launch() anyway, you can always make a custom test case class that extend XCTestCase, override the setUp there with this addition and then use that in all your test classes.

Rurik answered 29/9, 2015 at 10:32 Comment(2)
How do you perform different test cases? As example, if I want to test login success and login fail, the best practice would be to stub the two different responses... how do you handle something like this with your logic? I can only understand if I'm running in "app realm" or "test realm", but I cannot handle different "test realm" responses.Linares
Fake users? "user" + "wrongpassword" = fail, "user" + "password" = success.Rurik

© 2022 - 2024 — McMap. All rights reserved.