Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to test IBAction?

Tags:

ios

xctest

ocunit

It is kinda easy to unit test IBOutlets, but how about IBActions? I was trying to find a way how to do it, but without any luck. Is there any way to unit test connection between IBAction in a View Controller and a button in the nib file?

like image 996
sash Avatar asked Sep 09 '13 13:09

sash


3 Answers

For full unit testing, each outlet/action needs three tests:

  1. Is the outlet hooked up to a view?
  2. Is the outlet connected to the action we want?
  3. Invoke the action directly, as if it had been triggered by the outlet.

I do this all the time to TDD my view controllers. You can see an example in this screencast.

It sounds like you're asking specifically about the second step. Here's an example of a unit test verifying that a touch up inside myButton will invoke the action doSomething: Here's how I express it using OCHamcrest. (sut is a test fixture for the system under test.)

- (void)testMyButtonAction {
    assertThat([sut.myButton actionsForTarget:sut
                              forControlEvent:UIControlEventTouchUpInside],
               contains(@"doSomething:", nil));
}

Alternatively, here's a version without Hamcrest:

- (void)testMyButtonAction {
    NSArray *actions = [sut.myButton actionsForTarget:sut
                              forControlEvent:UIControlEventTouchUpInside];
    XCTAssertTrue([actions containsObject:@"doSomething:"]);
}
like image 199
Jon Reid Avatar answered Nov 01 '22 19:11

Jon Reid


I did it using OCMock, like this:

MyViewController *mainView =  [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
[mainView view];

id mock =  [OCMockObject partialMockForObject:mainView];

//testButtonPressed IBAction should be triggered
[[mock expect] testButtonPressed:[OCMArg any]];

//simulate button press 
[mainView.testButton sendActionsForControlEvents: UIControlEventTouchUpInside];

[mock verify];

If IBAction is not connected, the test will fail with error "expected method was not invoked".

like image 40
sash Avatar answered Nov 01 '22 20:11

sash


Here is what I use in Swift. I created a helper function that I can use in all my UIViewController unit tests:

func checkActionForOutlet(outlet: UIButton?, actionName: String, event: UIControlEvents, controller: UIViewController)->Bool{
    if let unwrappedButton = outlet {
        if let actions: [String] = unwrappedButton.actionsForTarget(controller, forControlEvent: event)! as [String] {
            return(actions.contains(actionName))
        }
    }
    return false
}

And then I just invoke it from the test like this:

func testScheduleActionIsConnected() {
    XCTAssertTrue(checkActionForOutlet(controller.btnScheduleOrder, actionName: "scheduleOrder", event: UIControlEvents.TouchUpInside, controller: controller ))
}

I am basically making sure that the button btnScheduleOrder has an IBAction associated with the name scheduleOrder for the event TouchUpInside. I need to pass the controller where the button is contained as a way to verify the target for the action as well.

You can also make it a little more sophisticated by adding some other else clause in case the unwrappedButton does not exist which means the outlet is not there. As I like to separate outlets and actions tests I don't have it included here

like image 36
Julio Bailon Avatar answered Nov 01 '22 20:11

Julio Bailon