2014-02-06 1 views
0

Я изо всех сил пытаюсь найти лучший метод тестирования взаимодействия с Core Data в фоновом потоке. У меня есть следующий метод класса:Тестирование фона, сохраняющего объект Core Data с Kiwi

+ (void)fetchSomeJSON 
{ 
    // Download some json then parse it in the block 
    [[AFHTTPClient sharedClient] fetchAllThingsWithCompletion:^(id results, NSError *error) { 
     if ([results count] > 0) { 

      NSManagedObjectContext *backgroundContext = //... create a new context for background insertion 
      dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 

      dispatch_async(background, ^{ // If I comment this out, my test runs just fine 

       //... insert and update some entities 
       for (NSString *str in results) { 
        NSManagedObject *object = //... 
       } 
      }); 
     } 
    }]; 
} 

Я в настоящее время тестирования этого метода со следующим кодом Киви:

describe(@"MyAction", ^{ 

    __block void (^completionBlock)(NSArray *array, NSError *error); 

    beforeEach(^{ 
     // Stub the http client 
     id mockClient = [AFHTTPClient mock]; 
     [WRNAPIClient stub:@selector(sharedClient) andReturn:mockClient]; 

     // capture the block argument 
     KWCaptureSpy *spy = [mockClient captureArgument:@selector(fetchAllThingsWithCompletion:) atIndex:0]; 
     [MyClass fetchSomeJSON]; // Call the method so we can capture the block 
     completionBlock = spy.argument; 

     // run the completion block 
     completionBlock(@[@"blah"], nil); 
    }) 

    // If I remove the dispatch_async block, this test passes fine. 
    // If I add it in again the test fails, probably because its not waiting 
    it(@"should return the right count", ^{ 
     // entityCount is a block that performs a fetch request count 
     NSInteger count = entityCount(moc, @"Task"); 
     [[theValue(count) should] equal:theValue(4)]; 
    }) 

    // This works fine, but obviously I don't want to wait a second 
    it(@"should return the right count after waiting for a second", ^{ 
     sleep(1); 
     NSInteger count = entityCount(moc, @"Task"); 
     [[theValue(count) should] equal:theValue(4)]; 
    }); 

}; 

Если я удалить dispatch_async линии, то я могу получить мой тест, чтобы быстро запустить , Единственный способ, которым я могу получить свой тестовый набор для запуска при использовании dispatch_async, - sleep(1) после вызова блока завершения. Использование sleep() заставляет меня думать, что я не подхожу к нему правильно. Я пробовал использовать shouldEventually, но это не похоже на повторное получение значения count.

ответ

2

Вы пробовали эти асинхронные макросы блока?

#define TestNeedsToWaitForBlock() __block BOOL blockFinished = NO 
#define BlockFinished() blockFinished = YES 
#define WaitForBlock() while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !blockFinished) 
+0

Я не проверял это, но это выглядит намного более лаконичным способом ожидания. – squarefrog

1

Я пробовал несколько подходов к решению этого, никто не чувствует себя хорошо.


1) Переместите dispatch_async свой класс

+ (void)dispatchOnMainQueue:(Block)block 
{ 
    if ([NSThread currentThread] == [NSThread mainThread]) { 
     block(); 
    } else { 
     dispatch_sync(dispatch_get_main_queue(), block); 
    } 
} 

+ (void)dispatchOnBackgroundQueue:(Block)block 
{ 
    dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 
    dispatch_async(background, block); 
} 

Затем во время выполнения теста, Swizzle фоновой депеши произойти на главную очереди. Это сработало, но было непредсказуемо. Это также было так неправильно!


2) Перемещение кода установки на Kiwi «ы beforeAll блока, затем спать основной поток. Это работает как тесты Kiwi, которые запускаются в основном потоке, поэтому мы эффективно говорим «давайте делать фоновые операции перед проведением тестов». Я думаю, что это то, что я собираюсь использовать. Да, это делает мои модульные тесты работают медленнее, но они проходят, когда они должны делать, и не когда они должны

describe(@"MyAction", ^{ 

    __block void (^completionBlock)(NSArray *array, NSError *error); 

    beforeAll(^{ 
     // Stub the http client 
     id mockClient = [AFHTTPClient mock]; 
     [WRNAPIClient stub:@selector(sharedClient) andReturn:mockClient]; 

     // capture the block argument 
     KWCaptureSpy *spy = [mockClient captureArgument:@selector(fetchAllThingsWithCompletion:) atIndex:0]; 
     [WRNTaskImporter importAllTasksFromAPI]; 
     completionBlock = spy.argument; 

     // run the completion block 
     completionBlock(@[@"blah"], nil); 

     // Wait for background import to complete 
     [NSThread sleepForTimeInterval:0.1]; 

    }) 

    // This works 
    it(@"should return the right count", ^{ 
     // entityCount is a block that performs a fetch request count 
     NSInteger count = entityCount(moc, @"Task"); 
     [[theValue(count) should] equal:theValue(4)]; 
    }) 
}; 

Оговорки этого подхода заключается в том, что он работает только тогда, когда вы не изменяете какие-либо данных перед тестом , Скажем, например, я вставляю 4 объекта и хочу проверить, что каждый объект был вставлен, как и ожидалось. Этот вариант будет работать здесь. Если мне нужно было повторно запустить метод импорта и проверить, что счетчик не увеличился, мне нужно будет добавить еще один [NSThread sleepForTimeInterval:0.1] после вызова кода вставки.


Для обычных блоков на основе Kiwi тестов вы, вероятно, следует использовать либо expectFutureValue shouldEventually метод или KWCaptureSpy, чтобы проверить свой код, но это не может помочь при вызове вложенных блоков.

Если у кого-то есть более подходящий метод для проверки таких случаев, я рад это слышать!

 Смежные вопросы

  • Нет связанных вопросов^_^