Модульное тестирование с помощью NSURLSession для OCMock

У меня есть сетевой класс под названием: 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: ответ должен содержать все альбомы и нулевую ошибку.

Другие моменты, которые следует иметь в виду, это то, что мне нужно имитировать NSURLSession с поддельными данными JSON «jsonResponse» для метода 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);
}];

person GameDev    schedule 26.11.2015    source источник


Ответы (1)


Хитрость с блоками заключается в том, что вам нужен тест для вызова блока с любыми аргументами, которые нужны тесту.

В 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
person Jon Reid    schedule 11.02.2016