OCMock: Почему я получаю нераспознанное исключение селектора при попытке вызвать макет UIWebView? - PullRequest
1 голос
/ 11 ноября 2010

Редактировать: Все это было вызвано опечаткой в ​​настройке других флагов ссылок. См. мой ответ ниже для получения дополнительной информации.


Я пытаюсь смоделировать UIWebView, чтобы я мог проверить, что методы на нем вызываются во время теста контроллера представления iOS. Я использую статическую библиотеку OCMock, созданную из версии 70 SVN (самая последняя на момент написания этого вопроса), и среду модульного тестирования Google Toolbox для Mac (GTM), версия 410 из SVN. Я получаю следующую ошибку, когда контроллер представления пытается вызвать ожидаемый метод.

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

Однако, как только я добавил закомментированное ожидание, ошибка вернулась при использовании частичного макета.

У меня есть другие тесты, которые успешно используют макеты в том же проекте.

Есть идеи? Поддерживается ли макет объектов UIKit OCMock?

Редактировать: Основываясь на совете в ответе ниже, я попробовал следующий тест, но я получаю ту же ошибку:

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

Ответы [ 2 ]

4 голосов
/ 14 ноября 2010

UIKit классы - загадочные звери, и я обнаружил, что дурачиться с издевательством над ними может привести к часам веселья отладки. Тем не менее, я обнаружил, что с небольшим терпением вы можете заставить его работать.

Первое, что я заметил в вашем коде, это то, что ваш контроллер не загружает свое представление в вашем тесте. Обычно я всегда проверяю загрузку представления перед выполнением любых тестов. Это, конечно, означает, что вы не можете написать ожидания для инициализации вашего веб-представления, но в этом случае вам это не нужно. Вы могли бы сделать это:

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

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

Тем не менее, если вы захотите затем потом смоделировать веб-представление, я бы порекомендовал использовать частичное макетирование для существующего веб-представления:

- (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, он почти всегда будет беспорядочно взорваться, когда вы попытаетесь сделать с ним что-то связанное с видом, например, добавить его в суперпредставление.

3 голосов
/ 16 ноября 2010

Это оказалось одной из тех проблем с одним персонажем, которые вы не замечали, пока не посмотрели несколько десятков раз.

За в этом посте на форумах OCMock я установил другие флаги компоновщика для цели моего модульного теста на -ObjC -forceload $(PROJECT_DIR)/Libraries/libOCMock.a. Это не верно; -forceload должно было быть -force_load. Как только я исправил эту опечатку, мои тесты сработали.

...