【问题标题】:Set readonly navigationController property on UIViewController for mocking在 UIViewController 上设置只读 navigationController 属性以进行模拟
【发布时间】:2010-12-22 23:20:18
【问题描述】:
我使用 OCMock 创建了一个模拟 UINavigationController。但是,我不能将它分配给 UIViewController 的 navigationController 属性,因为该属性是只读的。
id mockNavController = [OCMockObject mockForClass:[UINavigationController class]];
...
myViewController.navigationController = mockNavController; // readonly!
this blog post 的作者声称找到了解决方案,但忽略了分享。
【问题讨论】:
标签:
objective-c
cocoa-touch
unit-testing
ocmock
【解决方案1】:
不必创建允许您设置 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];
}
【解决方案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"];
});
【解决方案3】:
有几种可能的解决方案。
您可以为 navigationController 调用私有设置器,但它可能不存在或在所有情况下都不能可靠地工作。
您可以按照 Derek 的建议创建一个类别来重新定义 UIViewController 上的 navigationController 属性。访问 navigationController 属性应该是安全的,但如果 UIViewController 直接在任何地方访问支持 ivar 并且您没有在类别中使用相同的 ivar,那么您可能会看到意外行为。
您可以使用 UINavigationController 的部分模拟,如 http://blog.carbonfive.com/2010/03/10/testing-view-controllers/。在这种情况下,您的测试并不像您希望的那样孤立,但至少您的 UIViewController 超类和 UINavigationController 的私有行为应该保持不变。
【解决方案4】:
我在测试中使用的一种技术是定义一个类别,它将方法添加到主类,以便我可以访问内部属性。您可以尝试使用类别来合成 setter,但您可能需要知道保存导航控制器指针的变量名称。