While trying to TDD code that would otherwise use a UIGestureRecognizer, I found no way to programatically verify the target-action. Without this, I'm not certain I can properly test it.
If the gesture recognizer is setup in IB (with iOS 5+ support) the target-action is setup when the NIB/Storyboard loads; if done in code it uses initWithTarget:action:, both of which mean that no amount of mocking would be able to detect the target-action.
I'm out of ideas. If anyone has successfully test-driven a UIGestureRecognizer I could use the advice.
Unfortunately you're trying to inspect a framework class which wasn't written with testing in mind and so doesn't expose the state you want to verify. That is going to make it difficult to assert on the existence of the target-action pairs you want to check for. In such a case I have three options you might use, none of which are great solutions:
You might be able to subclass UIGestureRecognizer, override the target-action methods to save the registered pairs in a collection you can then expose to users of the class, and then call the superclass implementations of those methods. Unfortunately then you're introducing new classes just to make testing easier, have to remember to use them, and may have to cast from UIGestureRecognizer to your custom subclass depending on where you get a gesture recognizer reference from.
Alternately your test could swizzle new versions of the target-action methods into UIGestureRecognizer giving you a hook to track added targets. Just make sure to swap the original method implementations back into place when you're done or future tests will have unexpected behavior.
Finally you might be able to find a private API call which gives you a way to check the registered target-actions on the gesture recognizer. Just make sure that private API call remains only in your test code.
This is how I unit test tap gesture recognizer in Swift. The test makes sure the code that responds to the tap gesture is executed.
class OnTap: NSObject {
var closure: ()->()
init(view: UIView, gesture: UIGestureRecognizer, closure:() -> ()) {
self.closure = closure
super.init()
view.userInteractionEnabled = true
view.addGestureRecognizer(gesture)
gesture.addTarget(self, action: "didTap:")
}
func didTap(gesture: UIGestureRecognizer) {
closure()
}
}
class MyClass {
var onTap: OnTap?
var didTap = false
func setupTap() {
let myView = UIView()
onTap = OnTap(view: myView, gesture: UITapGestureRecognizer()) { [weak self] in
self?.didTap = true
}
}
}
class MyGestureTests: XCTestCase {
func testRepondToGesture() {
let obj = MyClass()
obj.setupTap()
obj.onTap?.didTap(UITapGestureRecognizer())
XCTAssert(obj.didTap)
}
}
Note, this is a unit testing technique. In addition, I use UI tests to make sure all pieces work together in sweet harmony.
I'd suggest to subclass the gesture recognizer as follows:
class PanGestureRecognizer: UIPanGestureRecognizer {
let initialTarget: Any?
let initialAction: Selector?
public override init(target: Any?, action: Selector?) {
initialTarget = target
initialAction = action
super.init(target: target, action: action)
}
}
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