×

HELLO, MY
NAME IS MARTIN
AND THIS IS
MY BLOG

Testing delegate methods with Specta, Expecta and OCMockito

Posted on

If you are an Objective-C developer and you are into unit testing, chances are that you have come across the fantastic Specta/Expecta framework. Together with OCMockito, there is no excuse for not testing every aspect of your classes public interface.

This is no introduction to unit testing, so I will not go into detail on how to use these frameworks. I would rather like to show you my approach on testing delegate methods, a quite common thing which I feel is not covered in detail in any of the docs.

Testing delegates – the code

I will give you the full code right away and explain it in detail after the break.

#import <Specta/Specta.h>
#import <Expecta/Expecta.h>
#define HC_SHORTHAND
#import <OCHamcrest/OCHamcrest.h>
#define MOCKITO_SHORTHAND
#import <OCMockito/OCMockito.h>

#import "MyClass.h"

SpecBegin(MyClassTests)

describe(@"Public interface", ^{
    __block MyClass *myClass; // The class you would like to test
    __block UIViewController <MyClassDelegate> *delegateVC; // Our mock view controller, note the delegate protocol!
    
    beforeAll(^{
        //// Init myClass
        myClass = [[MyClass alloc] init];
        
        //// Mock a UIViewController and set it as our MyClass delegate
        delegateVC = mockObjectAndProtocol([UIViewController class], @protocol(MyClassDelegate));
        myClass.delegate = delegateVC;
    });

    it(@"should call my delegate method", ^{
        //// Call method on myClass that will trigger the delegate
        [myClass willTriggerMyDelegateMethod];

        //// Passes if myDelegateMethod has been called
        [verify(delegateVC) myDelegateMethod];        
    });

    it(@"should call my delegate method only once", ^{
        //// Call method on myClass that will trigger the delegate
        [myClass willTriggerMyOtherDelegateMethod];

        //// Passes if myOtherDelegateMethod has been called once
        [verifyCount(delegateVC, times(1)) myOtherDelegateMethod];
    });

    it(@"should call my delegate method with argument(s)", ^{
        //// Call method on myClass that will trigger the delegate
        [myClass willTriggerMyThirdDelegateMethod];

        //// Passes if myThirdDelegateMethod has been called once
        //// returning 1 as an argument
        [verifyCount(delegateVC, times(1)) myThirdDelegateMethod:1];

        //// Passes if myThirdDelegateMethod has been called once
        //// with any argument
        [verifyCount(delegateVC, times(1)) myThirdDelegateMethod:(NSUInteger)anything()];
    });
});

SpecEnd

As you can see, the important parts here are the creation of our mock UIViewController, the controller which serves as the delegate for our MyClass Testclass.
Firstly, we create a variable for it in the test description, __block storage so it is shared across our test blocks. Note that our ViewController adheres to the MyClassDelegate protocol, which is defined in MyClass.

__block UIViewController <MyClassDelegate> *delegateVC;

Then we instantiate our mock view controller in the beforeAll block, which is called only once before our tests are executed.

delegateVC = mockObjectAndProtocol([UIViewController class], @protocol(MyClassDelegate));
myClass.delegate = delegateVC;

This way, our mock class implements the MyClassDelegateProtocol and whenever a delegate method is triggered, our delegate class will record its invocation, thanks to OCMockito.
In order to check if and how many times our delegate method was called, we can simply say:

//// Without arguments
[verifyCount(delegateVC, times(1)) myDelegateMethod];

//// With NSUInteger as an argument
[verifyCount(delegateVC, times(1)) myDelegateMethodWithIndex:1];

//// With any argument, called at least once
[verifyCount(delegateVC, atLeastOnce()) myDelegateMethodWithIndex:(NSUInteger)anything()];

//// With multiple arguments
[verifyCount(delegateVC, times(1)) myDelegateMethod:myClass andIndex:1];

That’s it!

This approach works for me, but please let me know if you have any comments, ideas or improvements. Happy testing!

Troubleshooting

verify() might not work from scratch, if it gives you an error please select the test target, go to build settings and set EnableModules to No. You can find this in the OCMockito docs as well.
Moreover, since XCode 6 there seems to be a problem with showing the completed tests in the sidebar. Restarting XCode seems to be the only solution by now.