【问题标题】:iOS unit test: How to set/update/examine firstResponder?iOS 单元测试:如何设置/更新/检查 firstResponder?
【发布时间】:2011-08-30 01:28:00
【问题描述】:

如何编写第一响应者单元测试?

我正在尝试编写一个测试来确认一个方法将焦点推进到下一个文本字段。 controllerUIViewController 的后代。但是这个探索性测试失败了:

- (void)testFirstResponder
{
    [controller view];
    [[controller firstTextField] becomeFirstResponder];

    STAssertTrue([[controller firstTextField] isFirstResponder], nil);
}

第一行导致视图被加载,以便其出口就位。文本字段非零。但测试永远不会通过。

我猜becomeFirstResponder 不会立即设置第一响应者,而是安排稍后进行。那么有没有好的方法来针对它编写单元测试呢?

从已接受答案的评论中提取答案……让事情运行一小段时间:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];

我还发现我需要创建一个 UIWindow 并将视图控制器的视图放入其中,如最受好评的答案中所述。

【问题讨论】:

    标签: iphone ios testing ocunit first-responder


    【解决方案1】:

    您需要确保 textField 安装在视图层次结构中。

    如果视图的 window 属性包含一个 UIWindow 对象,则它已安装在视图层次结构中;如果它返回 nil,则视图与任何层次结构分离。

    希望这会有所帮助....

    【讨论】:

    • 你是对的,UIWindow 是 nil。有没有办法在单元测试中改变它?推送视图控制器不起作用,因为它会安排事情,但是(正如我在对@sergio 的回复中指出的那样)还不足以进行单元测试。
    • 我现在有了答案。 +1 用于填写部分谜题。
    【解决方案2】:

    我猜想管理/更改第一响应者链是在主循环中以某种方式完成的,当 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:

    【讨论】:

    • 好主意,但是对于单元测试,将消息放在事件队列中不起作用,因为测试结束并且在断言之前夹具被拆除。反例:将STAssertTrue 更改为STAssertFalse 没有区别。
    • 对,这不起作用...作为进一步的提示,我刚刚发现您可以在测试条件之前运行一段时间的运行循环:[[NSRunLoop currentRunLoop] runUntilDate:fiveSecondsFromNow];(来自:@987654321 @)。我不完全确定这个解决方案是否真的“优雅”,但它应该可以工作并且可能适用于“较小”的情况(当你真的不需要弗兰克时)......
    • 太棒了!即使将运行时间指定为零也可以解决问题。
    【解决方案3】:

    使用 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 提到您可能需要在调用 becomeFirstResponder 后调用 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]],但我发现在我们的实例中不需要这样做。

    但是,您的里程可能会有所不同(甚至取决于您使用的 Xcode 版本),因此您可能需要也可能不需要包含此类调用。

    【讨论】:

    • 您是否发现在不将窗口设置为关键窗口的情况下也能正常工作?
    • 从 iOS 10.3 开始,它不会成为关键窗口。
    猜你喜欢
    • 1970-01-01
    • 2014-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-25
    • 2014-03-02
    • 1970-01-01
    相关资源
    最近更新 更多