IOS unit test: Как установить/обновить/проверить firstResponder?

Как вы пишете тесты первого ответчика?

Я пытаюсь написать тест, чтобы подтвердить, что метод продвигает фокус на следующее текстовое поле. controller является потомком UIViewController. Но этот пробный тест не проходит:

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

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

Первая строка заставляет просмотр загружаться так, чтобы его выходы были на месте. Текстовые поля не равны нулю. Но тест никогда не проходит.

Я предполагаю, что becomeFirstResponder не устанавливает первый ответчик сразу, но планирует его позже. Так есть хороший способ написать unit test против него?

Вызов ответа из комментария в принятом ответе... Пусть все будет работать в течение короткого времени:

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

Ответы

Ответ 1

Я предполагаю, что управление/изменение первой цепи ответчика каким-то образом выполняется в основном цикле, когда пользовательский интерфейс обновляется, готовясь к следующей обработке событий. Если эта гипотеза верна, я бы просто сделал следующее:

-(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

Используя Xcode 5.1 и XCTestCase, это работает нормально:

- (void)testFirstResponder
{      
  // Make sure the controller 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]);
}

Чтобы view/subview стал первым ответчиком, он должен быть частью иерархии представлений, что означает, что его свойство окна корневого представления должно быть установлено.

Джон и Серджио упомянут, что вам может потребоваться позвонить [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] после вызова becomeFirstResponder в вашем желаемом подразделении, но я обнаружил, что это не требуется в нашем экземпляре.

Однако ваш пробег может варьироваться (даже в зависимости от версии Xcode, которую вы используете), поэтому вам может понадобиться или не обязательно включать такой вызов.

Ответ 3

Вам нужно убедиться, что textField установлен в иерархии представлений.

Если свойство окна views содержит объект UIWindow, оно было установлено в иерархии представлений; если он возвращает nil, представление отделяется от любой иерархии.

Надеюсь, это поможет...