2015-11-26 3 views
5

Ich habe eine Networking-Klasse genannt: ITunesAlbumDataDownloaderUnit Testing mit NSURLSession für 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 

und ich brauche eine Einheit Test dafür zu schaffen, geschieht Folgendes:

  • The NSURLSession dataTaskWithRequest: completionHandler : Antwort wird verspottet die gefälschte JSON-Daten ich muss enthalten:

// Expected JSON Antwort

NSData *jsonResponse = [self sampleJSONData]; 
  • Das zurückgegebene Array aus dem öffentlichen Methode downloadDataWithURLString: completionHandler: response sollten alle Alben und Null-Fehler enthalten.

Andere Punkte im Auge zu entblößen ist, dass ich NSURLSession mit den gefälschten JSON-Daten „jsonResponse“ der downloadDataWithURLString verspotten müssen: completionHandler: Methode OHNE Aufrufen einer tatsächlichen Netzwerkanforderung.

Ich habe verschiedene verschiedene Dinge ausprobiert, aber ich kann es einfach nicht ausarbeiten, ich denke, es ist die Kombination von Vortäuschen der Anfrage und der Blöcke, die mich wirklich verwirren.

Hier zwei Beispiele meiner Testmethode ist, dass ich versucht (Ich habe versucht, tatsächlich eine Menge anderer Möglichkeiten, auch, aber das ist, was ich habe jetzt noch):

- (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]; 
} 

und

- (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]; 
      } 
     } 

Ich habe das Gefühl, dass in beiden Fällen bin ich wahrscheinlich weg von der Marke :(

Ich würde wirklich etwas Hilfe mit diesem.

Danke.

UPDATE 1:

Hier ist, was ich jetzt mit gekommen sind, aber müssen wissen, ob ich auf dem richtigen Weg bin oder noch einen Fehler zu machen?

Antwort

2

Der Trick mit Blöcken ist, dass Sie den Test benötigen, um den Block aufzurufen, mit welchen Argumenten auch immer der Test will.

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

Das ist praktisch:

In OCMock kann dies wie folgt durchgeführt werden. Aber ...


Der Nachteil ist, dass der Block sofort aufgerufen wird, wenn someMethodWithBlock: aufgerufen wird. Dies spiegelt oft nicht das Timing des Produktionscodes wider.

Wenn Sie den Aufruf des Blocks verzögern möchten, bis die aufrufende Methode abgeschlossen ist, erfassen Sie ihn.In OCMock, kann dies wie folgt geschehen:

__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 

Ich ziehe OCHamcrest's HCArgumentCaptor zu verwenden. OCMock unterstützt OCHCAMCREST-MATCHER, daher glaube ich, dass dies funktionieren sollte:

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