Objective c 在UIViewController上设置只读navigationController属性以进行模拟

Objective c 在UIViewController上设置只读navigationController属性以进行模拟,objective-c,cocoa-touch,unit-testing,ocmock,Objective C,Cocoa Touch,Unit Testing,Ocmock,我已经使用OCMock创建了一个模拟UINavigationController。但是,我无法将其分配给UIViewController的navigationController属性,因为该属性是只读的 id mockNavController = [OCMockObject mockForClass:[UINavigationController class]]; ... myViewController.navigationController = mockNavController; //

我已经使用OCMock创建了一个模拟UINavigationController。但是,我无法将其分配给UIViewController的navigationController属性,因为该属性是只读的

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

《声明》的作者发现了一个解决方案,但忽略了分享它。

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

有几种可能的解决方案

您可以为navigationController调用私有setter,但这可能不存在,也不可能在所有情况下都可靠地工作

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


您可以使用UINavigationController的部分模拟,如中所示。在这种情况下,您的测试并不像您希望的那样孤立,但至少UIViewController超类和UINavigationController的私有行为应该保持不变。

无需创建允许您设置navigationController属性的变体,因为您可以模拟返回它的访问器。我是这样做的:

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

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

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

谢谢,这是一个有趣的解决方案。