2012-08-12 11 views
7

Sto utilizzando OCMock per creare i mock nei miei test per la mia app iOS e mi piacerebbe creare mock di protocolli che non implementano tutti i metodi facoltativi.OCMock: protocolli di simulazione con metodi opzionali esclusi

Se non è chiaro quello che voglio dire ... ecco qualche codice:

// 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

Vorrei fare una domanda in cambio. Perché il mock dovrebbe restituire NO quando viene chiesto se risponde al metodo opzionale. Dopotutto, quel metodo è parte del protocollo. Sarebbe possibile prendere in giro una classe che implementa il protocollo? Se quella classe non implementa il metodo opzionale, la simulazione farebbe quello che ti aspetti. –

+0

Il motivo è che vorrei verificare che una classe si comporta correttamente quando ha un delegato che non implementa un metodo delegato opzionale. – extremeboredom

risposta

1

mi ha colpito questa limitazione pure. L'idea di base è quella di sovrascrivere respondsToSelector: (che NON PU be essere deriso in modo affidabile da OCMock).

Ho fatto la seguente classe che fa questo per voi. È quindi possibile utilizzarlo come segue:

estendono GCOCMockOptionalMethodSupportingObject, e implementare il protocollo

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

Se non si chiama markSelectorAsImplemented, allora il vostro classBeingTested otterrà NO per respondsToSleectorForThatMethod

ho messo il codice per questo qui. Sto usando questo con grande effetto. Grazie a jer su #iphonedev per avermi impostato su questo percorso (ignorando lo respondsToSelector era la sua idea, stavo facendo qualche pazzo aggiunta al metodo runtime - questo è molto più metafisico).

Ecco il codice

/** 
* 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

La cosa più semplice da fare è creare una classe che contiene i selettori si vuole implementato. Non c'è bisogno di alcuna implementazione. Quindi crei una simulazione di classe di quella classe invece di un protocollo di simulazione e la usi allo stesso modo.

Ad esempio:

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

id mock = OCMStrictClassMock([MyAwesomeImplementation class]);