UISwitch Unit test with sendActions(for: .valueChanged)

GOAL: I'm currently unit testing the behavior of my UISwitch when it is pressed.

ISSUE: My issue I that the sendActions(for: .valueChanged) that I use to programmatically change the UISwitch's state is not working as I imagined. It doesn't make it change from false to true (in my example).

WHAT DID I DO ?: I'm using the following path:

  1. I declared my UISwitch as true
  2. I used sendActions(for: .valueChanged) for changing it to false
  3. I finally used XCTAssertEqual() to check if it correctly equal to false as expected. (I also could have used XCTAssertFalse() to check directly if it was false.

Ps: I correctly passed loadViewIfNeeded() in my setUPWithError()

QUESTION: How can I test my UISwitch ?? Maybe using UI Tests ?

CODE: Here is my code:

    var sut: ViewController!
    
    override func setUpWithError() throws {
        super.setUp()
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        sut = storyboard.instantiateViewController(withIdentifier: "ViewController") as? ViewController
        sut.loadViewIfNeeded()
    }

    func testGenderButton_ShouldUpdateValueWhenPressed() {
        sut.genderSwitch.isOn = true
        sut.genderSwitch.sendActions(for: .valueChanged)
        XCTAssertEqual(sut.genderSwitch.isOn, false, "\(sut.genderSwitch.isOn) should be equal to false")
    }

Accepted Reply

This is a misunderstanding of what UIControl.sendActions(for:) is for –– it is not for telling the control to do something, it is for the control to tell its clients that something has happened.

UIControls call this method internally when an event happens – in the case of a UISwitch it calls the method with .valueChanged and .primaryActionTriggered when the user taps the switch to change its value.

The correct way to programmatically change the value of a UISwitch is to set its isOn property. However as a rule UIKit does not send events when programmatic changes are made (this is because we do not know developer intent).

Finally it is fraught with peril to unit test code that you do not own – you may be making assumptions about how things work that are incorrect or not guaranteed by the author. For something like this you probably want a UI test – using automation to tap the switch, and then verifying that your handler is called and that the data updates correctly. This more closely matches how a user interacts with your app, and avoids encoding assumptions about how a tap on a UISwitch becomes a .valueChanged event and finally a change to your data model.

Add a Comment

Replies

Could it be a timing issue ? sendAction not yet executed when you test ?

Could yo try to dispatch XCTAssertEqual in another thread with delay, just to see ?

  • Thank you for your answer @Claude31 👍🏻 I’m sorry but I don’t know how to do this… Do you have any documentation that I can look up to in order to do what you said ?

Add a Comment

This is a misunderstanding of what UIControl.sendActions(for:) is for –– it is not for telling the control to do something, it is for the control to tell its clients that something has happened.

UIControls call this method internally when an event happens – in the case of a UISwitch it calls the method with .valueChanged and .primaryActionTriggered when the user taps the switch to change its value.

The correct way to programmatically change the value of a UISwitch is to set its isOn property. However as a rule UIKit does not send events when programmatic changes are made (this is because we do not know developer intent).

Finally it is fraught with peril to unit test code that you do not own – you may be making assumptions about how things work that are incorrect or not guaranteed by the author. For something like this you probably want a UI test – using automation to tap the switch, and then verifying that your handler is called and that the data updates correctly. This more closely matches how a user interacts with your app, and avoids encoding assumptions about how a tap on a UISwitch becomes a .valueChanged event and finally a change to your data model.

Add a Comment