Модульный тест iOS: как установить / обновить / проверить firstResponder? - PullRequest
17 голосов
/ 21 мая 2011

Как вы пишете юнит-тесты первого респондента?

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

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

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

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

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

Подтверждение ответа из комментария в принятом ответе ... Пусть все пойдет на короткое время:

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

Я также обнаружил, что мне нужно создать окно UIWindow и перетащить в него представление контроллера представления, как указано в ответе с наибольшим количеством голосов.

Ответы [ 3 ]

22 голосов
/ 11 июня 2014

Используя 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]);
}

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

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

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

8 голосов
/ 28 мая 2011

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

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

7 голосов
/ 28 мая 2011

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

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

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

...