2012-08-12 6 views
7

Ich verwende OCMock zum Erstellen von Mocks in meinen Tests für meine iOS-App, und ich möchte Protokolle erstellen, die nicht alle optionalen Methoden implementieren.OCMock: Mocking-Protokolle mit optionalen Methoden ausgeschlossen

Wenn es nicht klar, was ich meine ... hier einige 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)]; 
+1

Lassen Sie mich eine Gegenfrage stellen. Warum sollte der Mock NEIN zurückgeben, wenn er gefragt wird, ob er auf die optionale Methode reagiert? Diese Methode ist immerhin Teil des Protokolls. Wäre es möglich, eine Klasse zu verspotten, die das Protokoll implementiert? Wenn diese Klasse die optionale Methode nicht implementiert, würde der Mock tun, was Sie erwarten. –

+0

Der Grund ist, dass ich gerne testen möchte, ob sich eine Klasse korrekt verhält, wenn sie einen Delegaten hat, der keine optionale Delegate-Methode implementiert. – extremeboredom

Antwort

1

ich diese Einschränkung auch getroffen. Die Grundidee besteht darin, respondsToSelector zu überschreiben: (was NICHT zuverlässig von OCMock verspottet werden kann).

Ich machte die folgende Klasse, die das für Sie tut. Sie können es dann wie folgt verwenden:

GCOCMockOptionalMethodSupportingObject erweitern und implementieren Ihr Protokoll

@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]; 

Wenn Sie nicht markSelectorAsImplemented nennen, dann classBeingTested erhalten NO für respondsToSleectorForThatMethod

Ich habe habe der Code dafür hier. Ich benutze das sehr gut. Danke an jer auf #iphonedev für mich auf diesem Weg (overriding respondsToSelector war seine Idee, ich habe einige verrückte Laufzeit Methode Zugabe - das ist viel sauberer methinks).

hier ist der 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 
1

Die einfachste Sache zu tun ist, um eine Klasse zu erstellen, um die Wähler enthält, die Sie implementiert haben sollen. Es muss keine Implementierung erfolgen. Dann erstellen Sie einen Klassenspott dieser Klasse anstelle eines Protokollspottes und verwenden ihn genauso.

Zum Beispiel:

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

id mock = OCMStrictClassMock([MyAwesomeImplementation class]);