Unit Testing with NSURLSession for OCMock

I have a network class: iTunesAlbumDataDownloader

@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 

and I need to create a Unit Test for this, which does the following:

  • NSURLSessionTaskWithRequest: completeHandler data: the response is being mocked to contain fake JSON data that I have:

// Expected JSON Response

 NSData *jsonResponse = [self sampleJSONData]; 
  • The returned array from the public method downloadDataWithURLString: completeHandler: the response should contain all albums and a nil error.

Other points worth considering are that I need to make fun of NSURLSession with fake JSON "jsonResponse" data for downloadDataWithURLString: completeHandler: method WITHOUT invoking the actual network request .

I tried different things, but I just can’t fix it, I think this is a combination of falsifying a request and blocks that really confuse me.

Here are two examples of my test method that I tried (I really tried many other ways, but this is what I have left right now):

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

and

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

I have a feeling that in both cases I'm probably not familiar with :(

I would really appreciate help on this.

Thanks.

UPDATE 1:

This is what I came up with now, but I need to know if I am on the right track or still making a mistake?

 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); }]; 
+5
source share
1 answer

Block trick - you need a test to call a block, with any arguments the test wants to run.

In OCMock, this can be done as follows:

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

It's comfortable. But…


The disadvantage is that the block will be called immediately when someMethodWithBlock: called. This often does not reflect production code time.

If you want to defer a block call until the call method completes, write it down. In OCMock, this can be done as follows:

 __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 

I prefer to use OCHamcrest's HCArgumentCaptor. OCMock supports OCHamcrest tokens, so I believe this should work:

 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 
+2
source

Source: https://habr.com/ru/post/1236872/


All Articles