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...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With