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, представление отделяется от любой иерархии.
Надеюсь, это поможет...
Ответ 4
Кажется, вместо модульного тестирования это работа для автоматизированного приемочного тестирования. (См. Фрэнк: автоматизированные тесты приемочных испытаний для iPhone и iPad)