EXC_BAD_ACCESS при доступе к параметрам в andDo OCMock
Я пытаюсь написать блок кода с помощью OCMock stub и методаDo.
В этом случае тестируется класс расширения UIImageView. Я хочу проверить, что расширение вызывает [self setImage:] с параметром, который не равен нулю (позже будет использовано другое сравнение изображений).
При использовании метода OCMock иDo тест завершается с EXC_BAD_ACCESS после завершения блока.
id mockView = [OCMockObject mockForClass:[UIImageView class]];
[[[mockView stub] andDo:^(NSInvocation *invocation)
{
UIImage *img;
[invocation getArgument:&img atIndex:2]; <---- line causing the exception
somebodySetImage |= (img != nil);
}] setImage:OCMOCK_ANY];
[mockView do_something_that_calls_setImage];
Единственное решение, которое я нашел сейчас, - это использование andCall вместо andDo, но это усложняет тест.
Можно ли избежать аварии с помощью &Do?
UPDATE
Хорошо, я постараюсь дать лучший пример здесь:
Вот новый фрагмент тестового кода:
- (void)testDownloadingThumbnail
{
PInfo *_sut = [[PInfo alloc] init];
__block id target = nil;
id mock = [OCMockObject mockForClass:[NSOperationQueue class]];
[[[mock expect] andDo:^(NSInvocation *inv)
{
NSInvocationOperation *op;
[inv getArgument:&op atIndex:2];
target = [[op invocation] target]; /* replacing this line with STAssert does not help either */
}] addOperation:OCMOCK_ANY];
[_sut setDownloadQueue:mock];
[_sut startDownloadingImagesAsync:YES];
[mock verify];
STAssertEqualObjects(target, _sut, @"invalid op target");
}
Вот проверенный код (единственный метод из PInfo):
- (void)startDownloadingImagesAsync:(bool)isThumbnailImg
{
NSInvocationOperation *inv;
inv = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(loadThumbnailWorker:)
object:nil];
[[self downloadQueue] addOperation:inv];
}
Код по-прежнему сбой при выходе из startDownloadingImagesAsync с EXC_BAD_ACCESS.
Если я добавлю точку останова внутри блока andDo, я вижу, что элемент управления достигает этой точки и возвращает правильные объекты через getArgument.
Тем не менее, если я использую getArgument внутри блока, он сбрасывает все, что я пытаюсь сделать.
P.S. Спасибо за помощь.
Ответы
Ответ 1
У меня возникла аналогичная проблема при использовании метода NSProxy forwardInvocation:.
Можете ли вы попробовать следующее?
NSInvocationOperation *op; // Change this line
__unsafe_unretained NSInvocationOperation *op; // to this line
Или другой подход может состоять в том, чтобы сохранить аргументы NSInvocation:
[invocation retainArguments];
http://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html#//apple_ref/occ/instm/NSInvocation/retainArguments
Я попытаюсь добавить более подробное объяснение позже.
Ответ 2
Я думаю, проблема в том, что вы пытаетесь напрямую вызвать макет. Для того, что вы пытаетесь сделать, вам не нужен макет. Просто вызовите метод и убедитесь, что изображение установлено:
expect(myObject.imageView.image).to.beNil();
[myObject do_something_that_calls_setImage];
expect(myObject.imageView.image).not.to.beNil();
Если вы действительно хотите использовать макет по какой-то причине, вы можете сделать это с помощью реального UIImageView
и частичного макета:
UIImageView *imageView = myObject.imageView;
id mockView = [OCMockObject partialMockForObject:imageView];
__block BOOL imageSet = NO;
[[[mockView stub] andDo:^(NSInvocation *invocation) {
UIImage *img;
[invocation getArgument:&img atIndex:2];
imageSet = (img != nil);
}] setImage:OCMOCK_ANY];
[myObject do_something_that_calls_setImage];
expect(imageSet).to.beTruthy();
Ответ 3
В моем случае это происходило, потому что я представил другой параметр этому методу, поэтому параметр блока был сдвинут на один.
Я исправил его, изменив [inv getArgument:&op atIndex:2]
на [inv getArgument:&op atIndex:3]