198

I'm writing some unit tests and, because of the nature of this particular app, it's important that I get as high up the UI chain as possible. So, what I'd like to do is programmatically trigger a button-press, as if the user had pressed the button in the GUI.

(Yes, yes -- I could just call the IBAction selector but, again, the nature of this particular app makes it important that I fake the actual button press, such that the IBAction be called from the button, itself.)

What's the preferred method of doing this?

TheNeil
  • 3,321
  • 2
  • 27
  • 52
Olie
  • 24,597
  • 18
  • 99
  • 131
  • Could you explain why it's important that you fake the button touch rather than call the action method directly? There really is no practical difference. – Jasarien Oct 27 '10 at 00:22
  • 1
    @Jasarien: well, because there IS a difference! ;) In my app, several buttons use the same action method, and the method parses how to behave based on the button's tag. So the unit test, in addition to testing one particular code-path through the method, (a) needs to test them all and (b) also verifies that the buttons are correctly wired. In addition, it nicely abstracts my unit-test suite, as I can define unit tests as lines in a text file with something like this: "1 + 2 = 3" where "3" is the expected ending display, and everything before it is which button to push. – Olie Oct 27 '10 at 13:54
  • well doesn't your action method have an (id)sender parameter? Couldn't you just call the method and pass the button as the sender? – Jasarien Oct 27 '10 at 16:09
  • 3
    Because of the abstract nature of my test harness, I don't *KNOW* the method that needs to be called for a particular button. I suppose I could dig into the button and get it, but doing sendActionsForControlEvents: does all this for me, with less opportunity for error, and keeps the test abstract and fitting with the design. – Olie Oct 28 '10 at 05:00

9 Answers9

482

It turns out that

[buttonObj sendActionsForControlEvents:UIControlEventTouchUpInside];

got me exactly what I needed, in this case.

EDIT: Don't forget to do this in the main thread, to get results similar to a user-press.


For Swift 3:

buttonObj.sendActions(for: .touchUpInside)
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Olie
  • 24,597
  • 18
  • 99
  • 131
  • Which is no different to calling `[myController myActionMethod:myButton1];` – Jasarien Oct 27 '10 at 16:19
  • @Jasarien is quite different, as it will send the touch event to all registered targets (listeners). – htafoya Jan 21 '13 at 23:15
  • 2
    Unfortunately, neither of these will always work - they rely on the receiver being a UIControl, rather than eg having a tap gesture. – tooluser May 09 '13 at 21:58
  • May i know how you set up your 'buttonObj'? How do you bind this to your UIButton? By ID? – 4 Leave Cover Nov 16 '14 at 13:51
  • @TanSiongZhe, `bottonObj` *is* the UIButton. Whatever your reference to your button -- be it IBOutlet, iVar or what have you -- calling `[myButton sendActionsForControlEvents: ...` will do the expected thing which, in my case, was the desired thing. – Olie Nov 16 '14 at 17:29
  • Hello @Olie, if I want only animation, without send actions. how can I do it? – benhi Mar 26 '15 at 11:01
  • @tooluser: `UIButton`s are always `UIControl`s, so it always works. @benhi: I'm not sure what you're asking, but it sounds like it might be a candidate for another SO question (rather than for discussion in comments.) – Olie Mar 26 '15 at 15:15
  • @Olie you are exactly right and I have no idea, on rereading, what I was thinking. My only guess is that I was thinking this doesn't fly for UIBarButtonItems, as they are a wrapper around buttons and not UIControls. . . . . whew. – tooluser Mar 30 '15 at 22:04
  • Um... @Olie this isn't a classroom. This is used by many people for their jobs, and they don't have time to do childish exercises... Thank you. – quemeful Dec 03 '15 at 12:17
  • @quemeful while I don't like the tone of your sentence, I do agree that we need @Olie to explain what's the difference between `[myController myActionMethod:myButton1];` and `[buttonObj sendActionsForControlEvents: UIControlEventTouchUpInside];` – Chen Li Yong Jan 21 '16 at 09:19
  • 1
    @ChenLiYong, One reason would be if the `myActionMethod:` is not public, and so you can't call it directly. – Iulian Onofrei Mar 22 '16 at 16:14
  • 1
    Xamarin: `buttonObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);` – Pierre Feb 01 '17 at 08:24
  • not working for me i have a button type custom rest is same as default – Abhishek Thapliyal Aug 13 '17 at 14:22
52

An update to this answer for Swift

buttonObj.sendActionsForControlEvents(.TouchUpInside)

EDIT: Updated for Swift 3

buttonObj.sendActions(for: .touchUpInside)
Hellojeffy
  • 1,851
  • 2
  • 19
  • 23
  • 1
    I would say that: buttonObj.sendActionsForControlEvents(.TouchUpInside) is even more Swift style ;) – Sajjon Feb 19 '16 at 12:25
  • 1
    I don't think this will work if the button is created in a XCTestCase without a run loop supporting. – CopperCash Apr 16 '18 at 08:50
8

Swift 3:

self.btn.sendActions(for: .touchUpInside)
Gobi M
  • 3,243
  • 5
  • 32
  • 47
6

If you want to do this kind of testing, you’ll love the UI Automation support in iOS 4. You can write JavaScript to simulate button presses, etc. fairly easily, though the documentation (especially the getting-started part) is a bit sparse.

Jeff Kelley
  • 19,021
  • 6
  • 70
  • 80
  • 1
    Yeah, that's cool stuff, but I'm trying to do it from within the app, not via Instruments (if that's the method you mean.) – Olie Oct 27 '10 at 13:50
  • Instruments runs with your app. The app is still running, Instruments just simulates the touching. – Jeff Kelley Oct 27 '10 at 16:57
  • 6
    I understand Instruments, Jeff -- what I'm saying is: I want my unit-tests to run in the device in my pocket, without a laptop or XCode or Instruments handy. Or do you know the secret of running instruments on the device? ;) – Olie Oct 28 '10 at 04:58
5

In this case, UIButton is derived from UIControl. This works for object derived from UIControl.

I wanted to reuse "UIBarButtonItem" action on specific use case. Here, UIBarButtonItem doesn't offer method sendActionsForControlEvents:

But luckily, UIBarButtonItem has properties for target & action.

 if(notHappy){        
         SEL exit = self.navigationItem.rightBarButtonItem.action;
         id  world = self.navigationItem.rightBarButtonItem.target;
         [world performSelector:exit];
 }

Here, rightBarButtonItem is of type UIBarButtonItem.

Hemang
  • 26,840
  • 19
  • 119
  • 186
Hitesh Savaliya
  • 1,336
  • 13
  • 15
  • if you use UIBarButtonItem, you can just [self doAction:self.navigationItem.rightBarButtonItem]; //-doAction: is self.navigationItem.rightBarButtonItem's toggle action name. – c0ming Aug 15 '13 at 09:50
5

For Xamarin iOS

btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);

Reference

Community
  • 1
  • 1
Durai Amuthan.H
  • 31,670
  • 10
  • 160
  • 241
3

Swift 5:

class ViewController: UIViewController {
    @IBOutlet weak var theTextfield: UITextField!
    @IBOutlet weak var someButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        theTextfield.text = "Pwd"
        someButton.sendActions(for: .touchUpInside)
    }

    @IBAction func someButtonTap(_ sender: UIButton) {
        print("button tapped")
    }
}
Naishta
  • 11,885
  • 4
  • 72
  • 54
2

It's handy for people who write Unit Tests without UI Tests ;-)

Swift 5 way to solve it for UIBarButtonItem, which does not have sendAction method like UIButton etc.

extension UIBarButtonItem {
    func sendAction() {
        guard let myTarget = target else { return }
        guard let myAction = action else { return }
        let control: UIControl = UIControl()
        control.sendAction(myAction, to: myTarget, for: nil)
    }
}

And now you can simply:

let action = UIBarButtonItem(title: "title", style: .done, target: self, action: #selector(doSomething))
action.sendAction()
Nat
  • 12,032
  • 9
  • 56
  • 103
-4

Swift 4:

self .yourButton(self)

elemento
  • 345
  • 3
  • 11