Как unit test делегат NSURLConnection?

Как я могу unit test передать делегат NSURLConnection? Я создал класс ConnectionDelegate, который соответствует различным протоколам, чтобы обслуживать данные из Интернета для разных ViewControllers. Прежде чем зайти слишком далеко, я хочу начать писать модульные тесты. Но я не знаю, как тестировать их как единицу без подключения к Интернету. Я хотел бы также, что я должен сделать, чтобы рассматривать асинхронные обратные вызовы.

Ответ 1

Это похоже на ответ Джона, но не помещал его в комментарий. Первый шаг - убедиться, что вы не создаете реальное соединение. Самый простой способ добиться этого - вывести создание соединения в метод factory, а затем заменить метод factory в своем тесте. При частичной макетной поддержке OCMock это может выглядеть так.

В вашем реальном классе:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
    return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

В вашем тесте:

id objectUnderTest = /* create your object */
id partialMock = [OCMockObject partialMockForObject:objectUnderTest];
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO];
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]];

Теперь, когда ваш тестируемый объект пытается создать URL-соединение, он фактически получает фиктивное соединение, созданное в тесте. Фиктивное соединение не должно быть действительным, потому что мы его не запускаем, и оно никогда не используется. Если ваш код действительно использует соединение, вы можете вернуть еще один макет, который издевается над NSURLConnection.

Второй шаг - вызвать метод на вашем объекте, который инициирует создание NSURLConnection:

[objectUnderTest doRequest];

Поскольку тестируемый объект не использует реальное соединение, мы теперь можем вызвать методы делегата из теста. Для NSURLResponse мы используем другой макет, данные ответа создаются из строки, определенной в другом месте теста:

int statusCode = 200;
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]];
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode];
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock];

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding];
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData];

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection];

Что это. Вы эффективно фальсифицировали все взаимодействия, с которыми тестируется объект с соединением, и теперь вы можете проверить, находится ли он в состоянии, в котором он должен находиться.

Если вы хотите увидеть какой-то "настоящий" код, посмотрите на тесты для класса из проекта CCMenu, который использует NSURLConnections. Это немного запутанно, потому что тестируемый класс также называется связью.

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup

Ответ 2

EDIT (2-18-2014): Я просто наткнулся на эту статью с более элегантным решением.

http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/

По существу, у вас есть следующий метод:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
        if([timeoutDate timeIntervalSinceNow] < 0.0)
            break;
    } while (!done);

    return done;
}

В конце вашего тестового метода вы убедитесь, что события не были исчерпаны:

STAssertTrue([self waitForCompletion:5.0], @"Timeout");

Основной формат:

- (void)testAsync
{
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) {
        STAssertNotNil(result);
        done = YES;
    }
    onError:^(NSError *error) [
        STFail();
        done = YES;
    }

    // 2. Determine timeout
    STAssertTrue([self waitForCompletion:5.0], @"Timeout");
}    

==============

Я опаздываю на вечеринку, но натолкнулся на очень простое решение. (Большое спасибо http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)

.h файл:

@property (nonatomic) BOOL isDone;

.m file:

- (void)testAsynchronousMethod
{
    // 1. call method which executes something asynchronously.

    // 2. let the run loop do its thing and wait until self.isDone == YES
    self.isDone = NO;
    NSDate *untilDate;
    while (!self.isDone)
    {
        untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0]
        [[NSRunLoop currentRunLoop] runUntilDate:untilDate];
        NSLog(@"Polling...");
    }

    // 3. test what you want to test
}

isDone устанавливается в YES в потоке, выполняемом асинхронным методом.

Итак, в этом случае я создал и запустил NSURLConnection на шаге 1 и сделал его делегатом этот тестовый класс. В

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

Я установил self.isDone = YES;. Мы выходим из цикла while и выполняем тест. Готово.

Ответ 3

Я избегаю общения в модульных тестах. Вместо этого:

  • Я изолирую NSURLConnection внутри метода.
  • Я создаю подкласс тестирования, переопределяя этот метод, чтобы удалить все следы NSURLConnection.
  • Я пишу один тест, чтобы убедиться, что этот метод будет вызван, когда захочу. Тогда я знаю, что в реальной жизни он сгорит NSURLConnection.

Затем я сосредоточусь на более интересной части: синтезируйте mock NSURLResponses с различными характеристиками и передайте их методам NSURLConnectionDelegate.

Ответ 4

Мой любимый способ сделать это - подкласс NSURLProtocol и отвечать на все HTTP-запросы - или другие протоколы. Затем вы регистрируете протокол тестирования в методе -setup и отменяете его в своем методе -tearDown. Затем вы можете использовать этот тестовый протокол для получения некоторых хорошо известных данных, чтобы вы могли проверить его в своих модульных тестах.

Я написал несколько статей в блогах по этому вопросу. Наиболее важным для вашей проблемы, вероятно, будет Использование NSURLProtocol для вставки тестовых данных и Тестирование асинхронного сетевого доступа в блоке.

Вы также можете посмотреть мой ILCannedURLProtocol, который описан в предыдущих статьях. Источник доступен в Github.

Ответ 6

Вот что я делаю:

  • Получить панель управления XAMPP http://www.apachefriends.org/en/xampp.html
  • Запустите сервер Apache
  • В вашей папке ~/Sites поместите тестовый файл (любые данные, которые вы хотите, мы назовем его my file.test).
  • Запустите свой делегат, используя URL http://localhost/~username/myfile.test
  • Остановить сервер Apache, если он не используется.