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.swift
to 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...