你如何编写第一响应者单元测试?
我正在尝试编写测试以确认方法将焦点提升到下一个文本字段。 controller
是UIViewController
的后代。但是这种探索性测试失败了:
- (void)testFirstResponder
{
[controller view];
[[controller firstTextField] becomeFirstResponder];
STAssertTrue([[controller firstTextField] isFirstResponder], nil);
}
第一行导致视图被加载,以便其出口就位。文本字段是非零的。但测试从未通过。
我猜测becomeFirstResponder
没有立即设置第一个响应者,而是将其安排在以后。那么有一个很好的方法来对它进行单元测试吗?
在接受的答案中提取评论答案...... 让事情运行一段时间:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
我还发现我需要创建一个UIWindow并将视图控制器的视图放入其中,如最常用的答案中所述。
答案 0 :(得分:21)
使用Xcode 5.1和XCTestCase
,这似乎没问题:
- (void)testFirstResponder
{
// Make sure the controller's view has a window
UIWindow *window = [[UIWindow alloc] init];
[window addSubview:controller.view];
// Call whatever method you're testing
[controller.textView becomeFirstResponder];
// Assert that the desired subview is the first responder
XCTAssertTrue([sut.textView isFirstResponder]);
}
为了使视图/子视图成为第一响应者,它必须是 视图层次结构的一部分 ,这意味着它的根视图的窗口属性 设置。
Jon和Sergio提到您可能需要在所需的子视图上调用[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]
后致电becomeFirstResponder
,但我发现在我们的实例中不需要这样做。
但是,您的里程可能会有所不同(甚至取决于您使用的Xcode版本),因此您可能需要也可能不需要包含此类通话。
答案 1 :(得分:8)
我想在更新UI以准备下一个事件处理时,在主循环中以某种方式完成管理/更改第一个响应者链。如果这个假设是正确的,我会简单地执行以下操作:
-(void)assertIfNotFirstResponder:(UITextField*)field {
STAssertTrue([field isFirstResponder], nil);
}
- (void)testFirstResponder
{
[controller view];
[[controller firstTextField] becomeFirstResponder];
[self performSelector:@selector(@"assertIfNotFirstResponder:") withObject:[controller firstTextField] afterDelay:0.0];
}
注意:我使用了0.0延迟,因为我只是希望将消息放在事件队列中并尽快调度。我需要一种方法来回到主循环,因为它的内务管理。在您的情况下,这不会产生任何实际延迟。如果您正在执行多个相同类型的测试,即通过反复更改作为第一响应者的控件,此技术应该保证所有这些事件与performSelector
生成的事件正确排序。
如果您从其他线程运行测试,则可以使用– performSelectorOnMainThread:withObject:waitUntilDone:
答案 2 :(得分:7)
您需要确保textField安装在视图层次结构中。
如果视图的window属性包含UIWindow对象,则它已安装在视图层次结构中;如果它返回nil,则视图将从任何层次结构中分离。
希望这会有所帮助......