Drew H
Drew H

Reputation: 1302

How can you test the contents of a UIAlertAction handler with OCMock

I've got an app where I push a UIAlertController with several custom UIAlertActions. Each UIAlertAction performs unique tasks in the handler block of actionWithTitle:style:handler:.

I have several methods that I need to verify are executed within these blocks.

How can I execute the handler block so that I can verify that these methods are executed?

Upvotes: 2

Views: 2375

Answers (3)

Sishu
Sishu

Reputation: 1558

The following code will work on Swift 5.5 I would suggest to create a private DSL in the test target

private extension UIAlertController {

typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

func tapButtonAtIndex(index: Int) {
    let alertAction = actions[index]
    let block = alertAction.value(forKey: "handler")
    let handler = unsafeBitCast(block, to: AlertHandler.self)
    handler(alertAction)
  }
}

Upvotes: 0

Robert Atkins
Robert Atkins

Reputation: 24718

With a bit of clever casting, I've found a way to do this in Swift (2.2):

extension UIAlertController {

    typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

    func tapButtonAtIndex(index: Int) {
        let block = actions[index].valueForKey("handler")
        let handler = unsafeBitCast(block, AlertHandler.self)

        handler(actions[index])
    }

}

This allows you to call alert.tapButtonAtIndex(1) in your test and have the correct handler executed.

(I would only use this in my test target, btw)

Upvotes: 2

Drew H
Drew H

Reputation: 1302

After some playing around I finally figure it out. Turns out that the handler block can be cast as a function pointer and the function pointer can be executed.

Like so

UIAlertAction *action = myAlertController.actions[0];
void (^someBlock)(id obj) = [action valueForKey:@"handler"];
someBlock(action);

Here's an example of how it would be used.

-(void)test_verifyThatIfUserSelectsTheFirstActionOfMyAlertControllerSomeMethodIsCalled {

    //Setup expectations
    [[_partialMockViewController expect] someMethod];

    //When the UIAlertController is presented automatically simulate a "tap" of the first button
    [[_partialMockViewController stub] presentViewController:[OCMArg checkWithBlock:^BOOL(id obj) {

        XCTAssert([obj isKindOfClass:[UIAlertController class]]);

        UIAlertController *alert = (UIAlertController*)obj;

        //Get the first button
        UIAlertAction *action = alert.actions[0];

        //Cast the pointer of the handle block into a form that we can execute
        void (^someBlock)(id obj) = [action valueForKey:@"handler"];

        //Execute the code of the join button
        someBlock(action);
    }]
                                         animated:YES
                                       completion:nil];

   //Execute the method that displays the UIAlertController
   [_viewControllerUnderTest methodThatDisplaysAlertController];

   //Verify that |someMethod| was executed
   [_partialMockViewController verify];
}

Upvotes: 4

Related Questions