Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to TDD UIGestureRecognizers?

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.

like image 980
nheagy Avatar asked Jan 04 '12 05:01

nheagy


3 Answers

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.

like image 79
Jonah Avatar answered Nov 20 '22 06:11

Jonah


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.

First, I create an OnTap helper class

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()
  }
}

Next, I register a tap gesture wit a view and a callback 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
    }
  }
}

Finally, I simulate the tap in my unit test and check that the closure has been called

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.

like image 36
Evgenii Avatar answered Nov 20 '22 08:11

Evgenii


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)
    }

}
like image 1
Rudolf Adamkovič Avatar answered Nov 20 '22 08:11

Rudolf Adamkovič