在 OCMock 的 andDo 中访问参数时出现 EXC_BAD_ACCESS

Posted

技术标签:

【中文标题】在 OCMock 的 andDo 中访问参数时出现 EXC_BAD_ACCESS【英文标题】:EXC_BAD_ACCESS when accessing parameters in andDo of OCMock 【发布时间】:2012-11-07 11:10:02 【问题描述】:

我正在尝试使用 OCMock 的存根 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];

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

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

附:感谢您的帮助。

【问题讨论】:

你能详细说明[mockView do_something_that_calls_setImage];的意思吗? 大致如下:-(void)do_something_that_calls_setImage [self setImage:[UIImage imagedNamed:@"f.png"]; 我认为您需要发布更多代码。您不应该直接在mockView 上调用方法——它只是模拟的代理。我必须假设您的最后一行是对引用mockView 的其他对象的调用。如果您能澄清或解释您在测试中要达到的目标,我可能会提供帮助。 嗨,@ChristopherPickslay。我以某种方式错过了通知。在这种情况下,我正在尝试测试 mockView 的另一种方法:do_something_that_calls_setImage。该方法使用 mockView 自己的 setImage 方法。但这在您指定的情况下也会失败:在包含对 mockView 的引用的对象上调用 _do_something_that_calls_setImage_。我无法使用 andDo 的参数。尝试这样做总是会导致 EXC_BAD_ACCESS 只是好奇,你在用ARC吗? 【参考方案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

稍后我会尝试添加更详细的说明。

【讨论】:

这里奇怪的是我试图用“__block NSInvocationOperation *op;”将变量移到块之外然后尝试在块内使用。然而,测试崩溃了,尽管它不应该由 ARC 发布。我会很高兴知道这个解释。还有...非常感谢! 如果没有 __unsafe_unretained,对象会通过 getArgument: 方法分配给该地址,但 ARC 没有任何线索,因为它采用“void *”参数(而不是“id *”)。因此,ARC 不知道在发生这种情况时添加 -retain。但是,作为强类型的局部变量,它确实在超出范围时添加一个释放。因此,对象将被过度释放。最简单的方法是让局部变量为 __unsafe_unretained,这样 ARC 就不用管它了。 谢谢!对 getArgument:atIndex: 分配的局部变量使用 __unsafe_unretained 修复了我们的崩溃问题。请注意,[invocation retainArguments] 没有帮助。 [调用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:&amp;op atIndex:2] 更改为 [inv getArgument:&amp;op atIndex:3] 来修复它

【讨论】:

以上是关于在 OCMock 的 andDo 中访问参数时出现 EXC_BAD_ACCESS的主要内容,如果未能解决你的问题,请参考以下文章

为什么在尝试将指向数组的指针作为函数的参数时出现访问冲突错误?

使用字符串类型参数访问枚举时出现 TypeScript TS7015 错误

如何在ARC的OCMock中模拟**参数

从 Spotify 请求访问令牌时出现错误“仅支持有效的不记名身份验证”

将 OCMock 与 Facebook 一起使用

调用 [playerFactory play] 方法时出现错误访问错误