2011-02-03 16 views
5

Sto usando OCUnit Nel seguente metodo, come posso prendere in giro il mio oggetto WebService o il risultato nel metodo "startSynchronous" per poter scrivere un test di unità indipendente?Obiettivo C - Test unitario e oggetto di simulazione?

È possibile immettere del codice in là per creare un servizio Web fittizio o restituire dati simulati sulla chiamata startSynchronous?

risposta

4

Un modo è quello di utilizzare le categorie e sostituire i metodi che si desidera, si può anche eseguire l'override del metodo init per restituire un oggetto fittizio:

@interface Webservice (Mock) 
- (id)init; 
@end 

@implementation Webservice (Mock) 
- (id)init 
{ 
    //WebServiceMock is a subclass of WebService 
    WebServiceMock *moc = [[WebServiceMock alloc] init]; 
    return (Webservice*)moc; 
} 
@end 

Il problema di questo è che se si vuole fare il ritorno all'oggetto risultati diversi in diversi test in 1 file di test non è possibile farlo. (È possibile ignorare ogni metodo di una volta per pagina di prova)

EDIT:

Questa è una vecchia questione che ho postato, ho pensato di aggiornare la risposta a come scrivo codice verificabile e unità di test al giorno d'oggi:)

ViewController Codice

@implementation MyViewController 
@synthesize webService; 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    [self.webService sendSomeMessage:@"Some_Message"]; 
} 

- (WebService *)webService 
{ 
    if (!_webService) 
     _webService = [[WebService alloc] init]; 

    return _webService; 
} 

@end 

codice di prova

@implementation MyViewControllerTest 

- (void)testCorrectMessageIsSentToServer 
{ 
    MyViewController *vc = [[MyViewController alloc] init]; 
    vc.webService = [OCMock niceMockForClass:[WebService class]]; 

    [[(OCMockObject *)vc.webService expect] [email protected]"Some_Message"]; 
    [vc view]; /* triggers viewDidLoad */ 
    [[(OCMockObject *)vc.webService verify]; 
} 

@end 
+0

Non potreste avere un oggetto singleton come "TestConfiguration" dove dovreste raccogliere i risultati falsi desiderati per ogni chiamata, da nella tua categoria Mock? –

+0

Puoi esporre qualsiasi interno come proprietà, quindi puoi configurare la tua classe come desideri. –

1

Basandosi sulla risposta WebService di aryaxt, ecco un piccolo trucco per essere in grado di ottenere risultati diversi in test diversi.

In primo luogo, è necessario un oggetto Singleton che verrà utilizzata per memorizzare la risposta desiderata, proprio prima della prova TestConfiguration.h

#import <Foundation/Foundation.h> 
#import <objc/runtime.h> 
#import <objc/message.h> 


void MethodSwizzle(Class c, SEL orig, SEL new); 

@interface TestConfiguration : NSObject 


@property(nonatomic,strong) NSMutableDictionary *results; 

+ (TestConfiguration *)sharedInstance; 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
       selector:(SEL)selector; 


-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector; 
@end 

TestConfiguration.m

#import "TestConfiguration.h" 


void MethodSwizzle(Class c, SEL orig, SEL new) { 
    Method origMethod = class_getInstanceMethod(c, orig); 
    Method newMethod = class_getInstanceMethod(c, new); 
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 
     class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 
    else 
     method_exchangeImplementations(origMethod, newMethod); 
}; 

@implementation TestConfiguration 


- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     self.results = [[NSMutableDictionary alloc] init]; 
    } 
    return self; 
} 

+ (TestConfiguration *)sharedInstance 
{ 
    static TestConfiguration *sharedInstance = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     sharedInstance = [[TestConfiguration alloc] init]; 
     // Do any other initialisation stuff here 
    }); 
    return sharedInstance; 
} 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
      selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    [self.results setObject:result 
        forKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 
} 

-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    return [self.results objectForKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 

} 



@end 

Quindi definisci la tua categoria "Mock" per definire i metodi di simulazione, come ad esempio:

#import "MyWebService+Mock.h" 
#import "TestConfiguration.h" 

@implementation MyWebService (Mock) 


-(void)mockFetchEntityWithId:(NSNumber *)entityId 
          success:(void (^)(Entity *entity))success 
          failure:(void (^)(NSError *error))failure 
{ 

    Entity *response = (Entity *)[[TestConfiguration sharedInstance] getResultForCallToObject:self selector:@selector(fetchEntityWithId:success:failure:)]; 

    if (response == nil) 
    { 
     failure([NSError errorWithDomain:@"entity not found" code:1 userInfo:nil]); 
    } 
    else{ 
     success(response); 
    } 
} 

@end 

E, infine, nelle prove stesse, si sarebbe swizzle il metodo finto nel setup, e definire la risposta attesa in ciascuna prova, prima della chiamata

MyServiceTest.m

- (void)setUp 
{ 
    [super setUp]; 

    //swizzle webservice method call to mock object call 
    MethodSwizzle([MyWebService class], @selector(fetchEntityWithId:success:failure:), @selector(mockFetchEntityWithId:success:failure:)); 
} 

- (void)testWSMockedEntity 
{ 
    /* mock an entity response from the server */ 
    [[TestConfiguration sharedInstance] setNextResult:[Entity entityWithId:1] 
             forCallToObject:[MyWebService sharedInstance] 
               selector:@selector(fetchEntityWithId:success:failure:)]; 

    // now perform the call. You should be able to call STAssert in the blocks directly, since the success/error block should now be called completely synchronously. 
} 

Osservazioni : nel mio esempio, TestConfiguration utilizza la classe/selettore come chiave anziché oggetto/selettore. Ciò significa che ogni oggetto della classe utilizzerà la stessa risposta per il selettore. Questo è molto probabilmente il tuo caso, dato che i servizi web sono spesso singleton. Ma dovrebbe essere migliorato per un oggetto/selettore magari usando l'indirizzo di memoria dell'oggetto invece della sua classe