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

3

2

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?

Orthostichy answered 28/4, 2016 at 22:55 Comment(0)
O
4

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];
}
Orthostichy answered 28/4, 2016 at 22:55 Comment(2)
Any idea what the correct cast is in Swift? Simply casting to the expected function type gives an EXC_BAD_INSTRUCTION at runtime.Cissie
I wish I could upvote this answer twice. Big help for mocking around alert action handlers. I understand Apple could change how this works and my tests will break, but I'm willing to live with it for now.Dairying
C
2

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)

Cissie answered 15/9, 2016 at 13:57 Comment(1)
This fails in Swift 3.0.1 with fatal error: can't unsafeBitCast between types of different sizes, does anyone know how to fix this?Cissie
B
0

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)
  }
}
Brewery answered 9/11, 2022 at 20:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.