在UIViewController上设置readonly navigationController属性以进行模拟

时间:2010-12-22 23:20:19

标签: objective-c cocoa-touch unit-testing ocmock

我使用OCMock创建了一个模拟UINavigationController。但是,我不能将它分配给UIViewController的navigationController属性,因为该属性是只读的。

id mockNavController = [OCMockObject mockForClass:[UINavigationController class]];
...
myViewController.navigationController = mockNavController; // readonly!

this blog post的作者声称找到了一个解决方案但忽略了分享它。

4 个答案:

答案 0 :(得分:17)

没有必要创建一个允许您设置navigationController属性的mutator,因为您可以模拟返回它的访问者。我是这样做的:

-(void)testTappingSettingsButtonShouldDisplaySettings {
    MyController *myController = [[MyController alloc] init];

    // expect the nav controller to push a settings controller
    id mockNavigationController = [OCMockObject mockForClass:[UINavigationController class]];
    [[mockNavigationController expect] pushViewController:[OCMArg any] animated:YES];

    // set up myController to return the mocked navigation controller
    id mockController = [OCMockObject partialMockForObject:myController];
    [[[mockController expect] andReturn:mockNavigationController] navigationController];

    [myController settingsButtonTapped];

    [mockNavigationController verify];
    [mockController verify];
    [myController release];
}

答案 1 :(得分:2)

一个非常晚的回应,但是对于后人,我刚刚发现另一种方法是采用基于状态的方法并实际将测试中的视图控制器粘贴到真正的导航控制器中。然后,您可以通过检查导航控制器的状态来查看视图控制器并测试它对导航堆栈的作用。这是一个例子:

it(@"displays the station chooser when you tap the 'Choose station' button", ^{
    // Given
    LaunchViewController *launchViewController = [LaunchViewController newWithNearestStationLocator:nil];
    [launchViewController view];
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:launchViewController];

    // When
    [[launchViewController chooseStationBtn] sendActionsForControlEvents:UIControlEventTouchUpInside];

    // Then
    [[theValue(navController.viewControllers.count) should] equal:theValue(2)];
    [[NSStringFromClass(navController.visibleViewController.class) should] equal:@"StationsViewController"];
});

答案 2 :(得分:0)

我在测试中使用的一种技术是定义一个类,它将方法添加到主类,以便我可以访问内部属性。您可以尝试使用类别来合成一个setter,但您可能需要知道保存导航控制器指针的变量名称。

答案 3 :(得分:0)

有几种可能的解决方案。

您可以为navigationController调用私有setter,但在所有情况下都可能不存在或无法正常工作。

您可以按照Derek的建议创建一个类别,重新定义UIViewController上的navigationController属性。然后访问navigationController属性应该是安全的,但是如果UIViewController直接在任何地方访问支持ivar而你没有在你的类别中使用相同的ivar,那么你可能会看到意外的行为。

您可以像http://blog.carbonfive.com/2010/03/10/testing-view-controllers/中一样使用UINavigationController的部分模拟。在这种情况下,您的测试并不像您想要的那样孤立,但至少UIViewController超类和UINavigationController的私有行为应保持不变。