2015-11-26 21 views
5

ho una classe di rete denominata: ITunesAlbumDataDownloaderUnit Testing con NSURLSession per OCMock

@implementation AlbumDataDownloader 

- (void)downloadDataWithURLString:(NSString *)urlString 
       completionHandler:(void (^)(NSArray *, NSError *))completionHandler 
{   
    NSURLSession *session = [NSURLSession sharedSession]; 

    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString] 
              completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
    {   
     NSArray *albumsArray = [self parseJSONData:data]; 

     completionHandler(albumsArray, error); 
    }]; 

    [dataTask resume]; 
} 


    - (NSArray *)parseJSONData:(NSData *)data { 

     NSMutableArray *albums = [[NSMutableArray alloc] init]; 

... 
... 

     // Return the array 
     return [NSArray arrayWithArray:albums]; 
    } 

    @end 

e ho bisogno di creare uno unit test per questo che fa la seguente:

  • Il NSURLSession dataTaskWithRequest: completionHandler : risposta viene deriso per contenere i dati JSON falso che ho:

// risposta JSON atteso

012.
NSData *jsonResponse = [self sampleJSONData]; 
  • La matrice restituita dal metodo pubblico downloadDataWithURLString: completionHandler: risposta deve contenere tutti gli album ei errore nullo.

Altri punti da nudo in mente è che ho bisogno di prendere in giro NSURLSession con i dati falsi JSON "jsonResponse" al downloadDataWithURLString: completionHandler: Metodo senza invocare una richiesta di rete effettiva.

ho provato diverse cose diverse, ma non riesco proprio a lavorare fuori, penso che la sua combinazione di falsificare la domanda ei blocchi che è veramente mi ha confuso.

Ecco due esempi del mio metodo di prova che ho provato (in realtà ho provato un sacco di altri modi anche, ma questo è quello che ho rimanendo in questo momento):

- (void)testValidJSONResponseGivesAlbumsAndNilError { 

    // Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance 

    // Expected JSON response 
    NSData *jsonResponse = [self sampleJSONDataWithAlbums]; 

    id myMock = [OCMockObject mockForClass:[NSURLSession class]]; 

    [[myMock expect] dataTaskWithRequest:OCMOCK_ANY 
         completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) 
    { 

    }]; 

    [myMock verify]; 
} 

e

- (void)testValidJSONResponseGivesAlbumsAndNilError { 

       // Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance 

       // Expected JSON response 
       NSData *jsonResponse = [self sampleJSONDataWithAlbums]; 

      id myMock = [OCMockObject mockForClass:[AlbumDataDownloader class]]; 

      [[[myMock stub] andReturn:jsonResponse] downloadDataWithURLString:OCMOCK_ANY 
                  completionHandler:^(NSArray *response, NSError *error) 
      { 

      }]; 

      [myMock verify]; 
      } 
     } 

Ho la sensazione che in entrambi i casi sono probabilmente molto lontano dal segno :(

Apprezzerei molto l'aiuto.

Grazie.

UPDATE 1:

Ecco quello che ho adesso venuto con ma hanno bisogno di sapere se sono sulla strada giusta o ancora facendo un errore?

id mockSession = [OCMockObject mockForClass:[NSURLSession class]]; 
id mockDataTask = [OCMockObject mockForClass:[NSURLSessionDataTask class]]; 


[[mockSession stub] dataTaskWithRequest:OCMOCK_ANY 
         completionHandler:^(NSData _Nullable data, NSURLResponse Nullable response, NSError * Nullable error) 
{ 
    NSLog(@"Response: %@", response); 
}]; 


[[mockDataTask stub] andDo:^(NSInvocation *invocation) 
{ 
    NSLog(@"invocation: %@", invocation); 
}]; 

risposta

2

Il trucco con blocchi è necessario il test per chiamare il blocco, con gli argomenti che il test desidera.

In OCMock, questo può essere fatto in questo modo:

OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:@"First arg", nil])]); 

Questo è conveniente. Ma ...


Il rovescio della medaglia è che il blocco sarà richiamato immediatamente quando someMethodWithBlock: si chiama. Questo spesso non riflette i tempi del codice di produzione.

Se si vuole rinviare richiamo del blocco fino a dopo il metodo di invocazione completa, poi catturarlo.In OCMock, questo può essere fatto in questo modo:

__block void (^capturedBlock)(id arg1); 
OCMStub([mock someMethodWithBlock:[OCMArg checkWithBlock:^BOOL(id obj) { 
    capturedBlock = obj; 
    return YES; 
}]]); 

// ...Invoke the method that calls someMethodWithBlock:, then... 
capturedBlock(@"First arg"); // Call the block with whatever you need 

preferisco usare OCHamcrest's HCArgumentCaptor. OCMock supporta OCHamcrest matchers, quindi credo che questo dovrebbe funzionare:

HCArgumentCaptor *argument = [[HCArgumentCaptor alloc] init]; 
OCMStub([mock someMethodWithBlock:argument]); 

// ...Invoke the method that calls someMethodWithBlock:, then... 
void (^capturedBlock)(id arg1) = argument.value; // Cast generic captured argument to specific type 
capturedBlock(@"First arg"); // Call the block with whatever you need