Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OCMock: Mocking protocols with excluding optional methods

I'm using OCMock for creating mocks in my tests for my iOS app, and I'd like to create mocks of protocols that don't implement all of the optional methods.

If it's not clear what I mean... here's some code:

// Protocol definition
@protocol MyAwesomeProtocol
    - (void)doThatRequiredThing;
    @optional
    - (void)doThatOptionalThing;
@end

...

// In a test
id mock = [OCMockObject mockObjectForProtocol:@protocol(MyAwesomeProtocol)];

// This should return YES:
[mock respondsToSelector:@selector(doThatRequiredThing)];
// This should return NO:
[mock respondsToSelector:@selector(doThatOptionalThing)];
like image 478
extremeboredom Avatar asked Aug 12 '12 16:08

extremeboredom


2 Answers

I hit this limitation as well. The basic idea is to override respondsToSelector: (which CANNOT be reliably mocked by OCMock).

I made the following class which does this for you. You can then use it as follows:

extend GCOCMockOptionalMethodSupportingObject, and implement your protocol

@interface GCTestDelegate : GCOCMockOptionalMethodSupportingObject <GCDelegate>
@end

@implementation GCTestDelegate

//required methods
- (void)requiredMethod{
}
@end

// create your testdelegate
self.classBeingTested.delegate =  [OCMock partialMockForObject:[GCTestDelegate new]];
[self.classBeingTested.delegate markSelectorAsImplemented:@selector(optionalMethod:)];
[[self.classBeingTested.delegate expect] optionalMethod:self.classBeingTested];
[self.classBeingTested doSomethingThatwillCheckIfYourDelegateRespondsToYourOptionalMethod];

If you do not call markSelectorAsImplemented, then your classBeingTested will get NO for respondsToSleectorForThatMethod

I've put the code for it here. I'm using this to great effect. Thanks to jer on #iphonedev for setting me off on this path (overriding respondsToSelector was his idea, I was doing some crazy runtime method addition - this is much cleaner methinks).

here's the code

/**
 * This class is specifically useful and intended for testing code paths that branch
 * pending implementation of optional methods.
 * OCMock does not support mocking of protocols with unimplemented optional methods.
 * Further compounding the issue is the fact that OCMock does not allow mocking of
 * respondsToSelector (in fact, it does but the behaviour is undefined),
 * As such this class can be extending to implement a given protocol, the methods can be mocked/expected
 * as normal, but in addition we can tell the class to report it conforms to a protocol method or not.
 *
*/
@interface GCOCMockOptionalMethodSupportingObject : NSObject

- (void)markSelectorAsImplemented:(SEL)aSelector;
- (void)unmarkSelectorAsImplemented:(SEL)aSelector;


@end

#import "GCOCMockOptionalMethodSupportingObject.h"


@interface GCOCMockOptionalMethodSupportingObject ()
@property(nonatomic, strong) NSMutableArray *implementedSelectors;

@end

@implementation GCOCMockOptionalMethodSupportingObject {

}
//////////////////////////////////////////////////////////////
#pragma mark init 
//////////////////////////////////////////////////////////////

- (id)init {
    self = [super init];
    if (self) {
        self.implementedSelectors = [NSMutableArray array];
    }

    return self;
}

//////////////////////////////////////////////////////////////
#pragma mark public api
//////////////////////////////////////////////////////////////


- (void)markSelectorAsImplemented:(SEL)aSelector {
    if (![self isImplemented:aSelector]) {
        [self.implementedSelectors addObject:NSStringFromSelector(aSelector)];
    }
}


- (void)unmarkSelectorAsImplemented:(SEL)aSelector {
    for (NSString *selectorValue in [self.implementedSelectors mutableCopy]) {
        SEL storedSelector = NSSelectorFromString(selectorValue);
        if (sel_isEqual(aSelector, storedSelector)) {
            [self.implementedSelectors removeObject:selectorValue];
            break;
        }
    }
}


//////////////////////////////////////////////////////////////
#pragma mark private impl
//////////////////////////////////////////////////////////////


- (BOOL)isImplemented:(SEL)aSelector {
    for (NSString *selectorValue in self.implementedSelectors) {
        SEL storedSelector = NSSelectorFromString(selectorValue);
        if (sel_isEqual(aSelector, storedSelector)) {
            return YES;
        }
    }
    return NO;
}

//////////////////////////////////////////////////////////////
#pragma mark overridden
//////////////////////////////////////////////////////////////

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([self isImplemented:aSelector]) {
        return YES;
    } else {
        return [super respondsToSelector:aSelector];
    }
}

@end
like image 190
Infrid Avatar answered Nov 07 '22 12:11

Infrid


The easiest thing to do is to create a class containing the selectors you do want implemented. There doesn't need to be any implementation. Then you create a class mock of that class instead of a protocol mock and use it just the same way.

For example:

@interface MyAwesomeImplementation : NSObject <MyAwesomeProtocol>
- (void)doThatRequiredThing;
@end
@implementation MyAwesomeImplementation
- (void)doThatRequiredThing {}
@end

id mock = OCMStrictClassMock([MyAwesomeImplementation class]);
like image 35
Mark Baker Avatar answered Nov 07 '22 13:11

Mark Baker