OCMock: Mocking protocols with excluding optional methods
Asked Answered
S

2

7

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)];
Shanley answered 12/8, 2012 at 16:46 Comment(2)
Let me ask a question in return. Why should the mock return NO when asked whether it responds to the optional method. That method is part of the protocol after all. Would it be possible to mock a class that implements the protocol? If that class does not implement the optional method then the mock would do what you expect.Schnell
The reason is that I would like to test that a class behaves correctly when it has a delegate that does not implement an optional delegate method.Shanley
A
1

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
Applicant answered 22/10, 2012 at 15:29 Comment(0)
P
1

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]);
Pfaff answered 29/4, 2016 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.