Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XCode UI Testing swizzle API class methods

I have a simple app with two buttons that calls a JSON web service and prints out the result message.

I wanted to try the new XCode 7 UI Testing but I can't understand how to mock the API requests.

For simplicity I've built an example without actual requests nor any async operations.

I have the ZZSomeAPI.swift file in the main target:

import Foundation
public class ZZSomeAPI: NSObject {
  public class func call(parameter:String) -> Bool {
      return true
  }
}

Then my ZZSomeClientViewController.swift:

import UIKit
class ZZSomeClientViewController: UIViewController {
    @IBAction func buttonClick(sender: AnyObject) {
        print(ZZSomeAPI.call("A"))
    }
}

Now I've added a UITest target, recorded tapping the button and I have something like:

import XCTest
class ZZSomeClientUITests: XCTestCase {
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        XCUIApplication().launch()
    }

    func testCall() {
        let app = XCUIApplication()
        app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Button).elementBoundByIndex(0).tap()
    }        
}

So this works and running the test will print out true. But I want to include a test when the API returns false without messing the API. So, I added ZZSomeAPI.swiftto UI Tests target and tried method swizzling (UITest code updated):

import XCTest

class ZZSomeClientUITests: XCTestCase {

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        XCUIApplication().launch()
    }

    func testSwizzle() {
        XCTAssert(ZZSomeAPI.call("a"))
        XCTAssertFalse(ZZSomeAPI.callMock("a"))
        XCTAssert(ZZSomeAPI.swizzleClass("call", withSelector: "callMock", forClass: ZZSomeAPI.self))
        XCTAssertFalse(ZZSomeAPI.call("a"), "failed swizzle")
    }

    func testCall() {
        XCTAssert(ZZSomeAPI.swizzleClass("call", withSelector: "callMock", forClass: ZZSomeAPI.self))
        let app = XCUIApplication()
        app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Button).elementBoundByIndex(0).tap()
    }

}

extension NSObject {
    public class func swizzleClass(origSelector: String!, withSelector: String!, forClass:AnyClass!) -> Bool {
        var originalMethod: Method?
        var swizzledMethod: Method?

        originalMethod = class_getClassMethod(forClass, Selector(origSelector))
        swizzledMethod = class_getClassMethod(forClass, Selector(withSelector))

        if (originalMethod == COpaquePointer(bitPattern: 0)) { return false }
        if (swizzledMethod == COpaquePointer(bitPattern: 0)) { return false }

        method_exchangeImplementations(originalMethod!, swizzledMethod!)
        return true
    }
}

extension ZZSomeAPI {
    public class func callMock(parameter:String) -> Bool {
      return false
    }
}

So, testSwizzle() passes which means swizzling worked. But testCall() still prints true instead of false.
Is it because the swizzling is only done on the test target when UITest and main target are two different applications?
Is there any way around this?

I've found Mock API Requests Xcode 7 Swift Automated UI Testing but I'm not sure how to use launchArguments here.
In the example there's only one case, but I need to mock the call() method for different results for different test methods... If I use a launchArgument such as MOCK_API_RESPONSE containing the full response to be returned, the main target app delegate will have some "ugly test-only" code... Is there any way to check (in the main target) that it is being compiled for a UITest target so it only includes that code for that mocking launchArguments?

The cleanest option would be indeed to get swizzling to work...

like image 491
Filipe Pina Avatar asked Sep 16 '26 09:09

Filipe Pina


1 Answers

The Xcode UI tests execute in a separate application from your application. So changes to classes in the test runner application will not affect the classes in the tested application.

This is different from a unit test, where your tests run inside your application process.

like image 145
Mats Avatar answered Sep 17 '26 22:09

Mats



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!