使用NSURLSession进行单元测试的OCMock

6

我有一个网络课程,叫做: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

我需要创建一个单元测试,实现以下功能:

  • 模拟NSURLSession dataTaskWithRequest:completionHandler:响应,包含我准备好的虚假JSON数据:

// 预期的JSON响应

NSData *jsonResponse = [self sampleJSONData];
  • 公共方法downloadDataWithURLString:completionHandler:的返回数组应包含所有专辑和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];
}

并且

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

我有一种感觉,在这两种情况下,我可能都错了 :(
我真的很需要帮助。
谢谢。
更新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);
}];
1个回答

3
块的技巧在于需要测试调用块,并使用测试所需的任何参数。
在OCMock中,可以这样做:
OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:@"First arg", nil])]);

这很方便。但是...
缺点是当调用someMethodWithBlock:时,该块将立即被调用。这通常并不反映生产代码的时间安排。
如果想在调用方法完成后延迟调用该块,则需要捕获它。在 OCMock 中,可以像这样实现:
__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

我更喜欢使用OCHamcrest的 HCArgumentCaptor。OCMock支持OCHamcrest匹配器,因此我认为这应该可以工作:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接