使用NSURLSession进行OCMock单元测试

Gam*_*Dev 5 unit-testing objective-c ocmock stubs nsurlsession

我有一个名为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
Run Code Online (Sandbox Code Playgroud)

我需要为此创建一个单元测试,它执行以下操作:

  • NSURLSession dataTaskWithRequest:completionHandler:响应被模拟为包含我有的假JSON数据:

//预期的JSON响应

NSData *jsonResponse = [self sampleJSONData];
Run Code Online (Sandbox Code Playgroud)
  • 公共方法downloadDataWithURLString:completionHandler:response返回的数组应包含所有相册和nil错误.

要记住的其他要点是我需要使用假的JSON数据"jsonResponse"将NSURLSession模拟到downloadDataWithURLString:completionHandler:方法,而不调用实际的网络请求.

我尝试了各种不同的东西,但我无法解决这个问题,我认为这是伪造请求和块的组合,这让我很困惑.

这是我试过的两个测试方法的例子(我实际上尝试了很多其他的方法,但这就是我现在剩下的):

- (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];
}
Run Code Online (Sandbox Code Playgroud)

- (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];
            }
        }
Run Code Online (Sandbox Code Playgroud)

我有一种感觉,在这两种情况下我都可能有点不合适:(

我真的很感激这方面的一些帮助.

谢谢.

更新1:

以下是我现在提出的内容,但需要知道我是在正确的轨道上还是犯了错误?

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);
}];
Run Code Online (Sandbox Code Playgroud)

Jon*_*eid 3

块的技巧是您需要测试来调用块,并使用测试想要的任何参数。

\n\n

在 OCMock 中,可以这样完成:

\n\n
OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:@"First arg", nil])]);\n
Run Code Online (Sandbox Code Playgroud)\n\n

这很方便。但是\xe2\x80\xa6\n


\n缺点是该块将在以下情况下立即调用:someMethodWithBlock:被调用时将立即被调用。这通常不反映生产代码的时间。

\n\n

如果您想推迟调用该块直到调用方法完成之后,则捕获它。在 OCMock 中,可以这样完成:

\n\n
__block void (^capturedBlock)(id arg1);\nOCMStub([mock someMethodWithBlock:[OCMArg checkWithBlock:^BOOL(id obj) {\n    capturedBlock = obj;\n    return YES;\n}]]);\n\n// ...Invoke the method that calls someMethodWithBlock:, then...\ncapturedBlock(@"First arg"); // Call the block with whatever you need\n
Run Code Online (Sandbox Code Playgroud)\n\n

我更喜欢使用OCHamcrest'sHCArgumentCaptor。OCMock 支持 OCHamcrest 匹配器,所以我相信这应该有效:

\n\n
HCArgumentCaptor *argument = [[HCArgumentCaptor alloc] init];\nOCMStub([mock someMethodWithBlock:argument]);\n\n// ...Invoke the method that calls someMethodWithBlock:, then...\nvoid (^capturedBlock)(id arg1) = argument.value; // Cast generic captured argument to specific type\ncapturedBlock(@"First arg"); // Call the block with whatever you need\n
Run Code Online (Sandbox Code Playgroud)\n