15

我正在尝试使用 OCMock 的 stub andDo方法编写一段代码。

在这种情况下,正在测试 UIImageView 扩展类。我想检查扩展调用[self setImage:]的参数是否为非零(稍后将使用其他图像比较)。

使用 OCMock 的andDo方法时,测试在块完成后会因 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,但这会使测试复杂化。

我可以使用andDo避免崩溃吗?

更新 好吧,我将尝试在这里举一个更好的例子:这是新的测试代码:

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

使用 EXC_BAD_ACCESS从startDownloadingImagesAsync退出时,代码仍会崩溃。如果我在andDo块中添加断点,我会看到控件到达该点并通过getArgument检索正确的对象。

然而,如果我在块内使用getArgument,无论我尝试做什么,它都会崩溃。

PS感谢您的帮助。

4

3 回答 3

33

我在使用 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

稍后我将尝试添加更详细的解释。

于 2012-12-12T00:56:47.277 回答
0

我认为问题在于您试图直接调用模拟对象。对于你想要做的事情,你不应该需要一个模拟对象。只需调用该方法并验证图像是否已设置:

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();
于 2012-11-26T05:42:21.690 回答
0

在我的例子中,这是因为我在这个方法中引入了另一个参数,所以块参数被移动了一个。

我通过更改[inv getArgument:&op atIndex:2][inv getArgument:&op atIndex:3]

于 2017-07-13T07:13:21.010 回答