OCMock:为啥在尝试调用 UIWebView 模拟时会出现无法识别的选择器异常?

Posted

技术标签:

【中文标题】OCMock:为啥在尝试调用 UIWebView 模拟时会出现无法识别的选择器异常?【英文标题】:OCMock: Why do I get an unrecognized selector exception when attempting to call a UIWebView mock?OCMock:为什么在尝试调用 UIWebView 模拟时会出现无法识别的选择器异常? 【发布时间】:2010-11-11 14:56:12 【问题描述】:

编辑:这都是由我的“其他链接标志”设置中的拼写错误引起的。请参阅my answer below 了解更多信息。


我正在尝试模拟 UIWebView,以便我可以验证在测试 ios 视图控制器期间是否调用了其上的方法。我正在使用从 SVN 修订版 70(截至本问题发布时的最新版本)构建的 OCMock 静态库,以及来自 SVN 的 Google Toolbox for Mac (GTM) 单元测试框架修订版 410。当视图控制器尝试调用预期的方法时,我收到以下错误。

Test Case '-[FirstLookViewControllerTests testViewDidLoad]' started.
2010-11-11 07:32:02.272 Unit Test[38367:903] -[NSInvocation getArgumentAtIndexAsObject:]: unrecognized selector sent to instance 0x6869ea0
2010-11-11 07:32:02.277 Unit Test[38367:903] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSInvocation getArgumentAtIndexAsObject:]: unrecognized selector sent to instance 0x6869ea0'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x010cebe9 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x012235c2 objc_exception_throw + 47
    2   CoreFoundation                      0x010d06fb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
    3   CoreFoundation                      0x01040366 ___forwarding___ + 966
    4   CoreFoundation                      0x0103ff22 _CF_forwarding_prep_0 + 50
    5   Unit Test                           0x0000b29f -[OCMockRecorder matchesInvocation:] + 216
    6   Unit Test                           0x0000c1c1 -[OCMockObject handleInvocation:] + 111
    7   Unit Test                           0x0000c12a -[OCMockObject forwardInvocation:] + 43
    8   CoreFoundation                      0x01040404 ___forwarding___ + 1124
    9   CoreFoundation                      0x0103ff22 _CF_forwarding_prep_0 + 50
    10  Unit Test                           0x0000272a -[MyViewController viewDidLoad] + 100
    11  Unit Test                           0x0000926c -[MyViewControllerTests testViewDidLoad] + 243
    12  Unit Test                           0x0000537f -[SenTestCase invokeTest] + 163
    13  Unit Test                           0x000058a4 -[GTMTestCase invokeTest] + 146
    14  Unit Test                           0x0000501c -[SenTestCase performTest] + 37
    15  Unit Test                           0x000040c9 -[GTMIPhoneUnitTestDelegate runTests] + 1413
    16  Unit Test                           0x00003a87 -[GTMIPhoneUnitTestDelegate applicationDidFinishLaunching:] + 197
    17  UIKit                               0x00309253 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1252
    18  UIKit                               0x0030b55e -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 439
    19  UIKit                               0x0030aef0 -[UIApplication _run] + 452
    20  UIKit                               0x0031742e UIApplicationMain + 1160
    21  Unit Test                           0x0000468c main + 104
    22  Unit Test                           0x000026bd start + 53
    23  ???                                 0x00000002 0x0 + 2
)
terminate called after throwing an instance of 'NSException'
/Users/gjritter/src/google-toolbox-for-mac-read-only/UnitTesting/RunIPhoneUnitTest.sh: line 151: 38367 Abort trap              "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents

我的测试代码是:

- (void)testViewDidLoad 
    MyViewController *viewController = [[MyViewController alloc] init];

    id mockWebView = [OCMockObject mockForClass:[UIWebView class]];
    [[mockWebView expect] setDelegate:viewController];

    viewController.webView = mockWebView;

    [viewController viewDidLoad];
    [mockWebView verify];
    [mockWebView release];

我的视图控制器代码是:

- (void)viewDidLoad 
    [super viewDidLoad];
    webView.delegate = self;

我确实发现,如果我改为使用,测试会成功运行:

- (void)testViewDidLoad 
    MyViewController *viewController = [[MyViewController alloc] init];

    id mockWebView = [OCMockObject partialMockForObject:[[UIWebView alloc] init]];
    //[[mockWebView expect] setDelegate:viewController];

    viewController.webView = mockWebView;

    [viewController viewDidLoad];
    [mockWebView verify];
    [mockWebView release];

但是,只要我添加了被注释掉的期望,使用部分模拟时就会返回错误。

我有其他测试在同一个项目中成功使用了模拟。

有什么想法吗? OCMock 是否支持模拟 UIKit 对象?

编辑:根据以下答案中的建议,我尝试了以下测试,但我得到了同样的错误:

- (void)testViewDidLoadLoadsWebView 
    MyViewController *viewController = [[MyViewController alloc] init];
    UIWebView *webView = [[UIWebView alloc] init];

    // This test fails in the same fashion with or without the next line commented
    //viewController.view;

    id mockWebView = [OCMockObject partialMockForObject:webView];
    // When I comment out the following line, the test passes
    [[mockWebView expect] loadRequest:[OCMArg any]];

    viewController.webView = mockWebView;

    [viewController viewDidLoad];
    [mockWebView verify];
    [mockWebView release];

【问题讨论】:

【参考方案1】:

UIKit 类是神秘的野兽,我发现到处嘲笑它们可以带来数小时的调试乐趣。也就是说,我发现只要有一点耐心,你就可以让它发挥作用。

我注意到你的代码的第一件事是你的控制器没有在你的测试中加载它的视图。我通常确保始终在任何测试运行之前强制加载视图。当然,这意味着您不能为 Web 视图的初始化编写期望,但在这种情况下,您实际上并不需要。你可以这样做:

- (void)testViewDidLoadSetsWebViewDelegateToSelf 
    MyViewController *viewController = [[MyViewController alloc] init];
    // Force the view to load
    viewController.view;

    assertThat(controller.webView.delegate, equalTo(controller));

也就是说,如果您确实想随后模拟 Web 视图,我建议对现有 Web 视图使用部分模拟:

- (void)testWebViewDoesSomething 
    MyViewController *viewController = [[MyViewController alloc] init];
    // Force the view to load
    viewController.view;

    id mockWebView = [OCMockObject partialMockForObject:controller.webView];
    [[mockWebView expect] someMethod];

    [controller doWhatever];

    [mockWebView verify];

事实上,我发现最好总是对任何 UIView 子类使用部分模拟。如果你创建一个 UIView 的完整模拟,当你尝试做一些与它相关的事情时,它几乎总是会崩溃,比如将它添加到超级视图中。

【讨论】:

很抱歉尚未接受或评论您的答案。一个长周末后我刚回到工作岗位,今天晚些时候可以测试一下。 你是对的,我给出的示例测试不需要模拟。我发布了一个需要模拟的新示例测试。我尝试使用 viewController.view 强制加载视图,但执行此操作时仍然出现相同的错误。 抱歉浪费您的时间 - 我的问题是我的其他链接器标志中的一个错字。我赞成您的回答,因为它提供了有用的观点,即我的问题中的原始测试甚至不需要使用模拟。最好只在需要的情况下使用模拟。【参考方案2】:

事实证明,这是您在看了几十遍后才注意到的一个字符问题之一。

根据 OCMock 论坛上的 this post,我已将单元测试目标的其他链接器标志设置为 -ObjC -forceload $(PROJECT_DIR)/Libraries/libOCMock.a。这是错误的; -forceload 应该是 -force_load。一旦我修正了这个错字,我的测试就成功了。

【讨论】:

以上是关于OCMock:为啥在尝试调用 UIWebView 模拟时会出现无法识别的选择器异常?的主要内容,如果未能解决你的问题,请参考以下文章

OCMock,为啥我不能期望协议上的方法?

使用具有未知数量的方法调用的 OCMock

在现代风格的 OCMock 中使用 ignoringNonObjectArgs 时崩溃

使用从测试方法调用的方法的类的 OCMock

OCMock 验证失败

为啥我不能直接将 int 或 bool 传递给 OCMock 的“andReturnValue”参数?