Как проверить привязку пользовательского интерфейса между переменной RxSwift и наблюдаемым RxCocoa?

У меня есть простой ViewModel с одним свойством:

class ViewModel {
    var name = Variable<String>("")
}

И привязываю его к UITextField в моем ViewController:

class ViewController : UIViewController {

    var viewModel : ViewModel!

    @IBOutlet weak var nameField : UITextField!

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Binding
        nameField.rx.text
            .orEmpty
            .bind(to: viewModel.name)
            .disposed(by: disposeBag)
    }
}

Теперь я хочу протестировать его.

Я могу заполнить свойство nameField.text через Rx, используя событие .onNext - nameField.rx.text.onNext("My Name Here").

Но viewModel.name не заполняется. Почему? Как я могу заставить это поведение Rx работать в моем XCTestCase?

См. Ниже результат моего последнего пробного запуска:

Снимок экрана последнего тестового запуска


person Rici    schedule 15.02.2018    source источник
comment
Компоненты пользовательского интерфейса почти всегда создают проблемы при попытке пройти модульное тестирование, поэтому они не подходят для этой задачи. Вместо этого проверьте свою бизнес-логику и позвольте QA выполнить проверки пользовательского интерфейса.   -  person Cristik    schedule 15.02.2018
comment
Хороший звонок @Cristik, я приму твой совет. Мне все еще любопытно, почему Rx не увольняют   -  person Rici    schedule 15.02.2018
comment
Я рекомендую использовать библиотеку RxTest и TestScheduler вместо GCD. Затем вы можете запланировать действие в конкретной команде, и у вас будет гораздо больше контроля.   -  person damianesteban    schedule 16.02.2018
comment
Также Variable устарел. У вас есть BehaviorRelay и PublishRelay в качестве замены.   -  person Valérian    schedule 16.02.2018


Ответы (2)


Я считаю, что проблема в том, что привязка явно не запланирована в основном потоке. Я рекомендую использовать Driver в этом случае:

class ViewModel {
  var name = Variable<String>("")
}

class ViewController: UIViewController {

  let textField = UITextField()
  let disposeBag = DisposeBag()
  let vm = ViewModel()

  override func viewDidLoad() {
    super.viewDidLoad()
    textField.rx.text
        .orEmpty
        .asDriver()
        .drive(vm.name)
        .disposed(by: disposeBag)
}

// ..

С Driver привязка всегда будет происходить в основном потоке, и это не приведет к ошибке.

Кроме того, в вашем тесте я бы назвал skip для привязки:

sut.nameTextField.rx.text.skip(1).onNext(name)

потому что Driver будет воспроизводить первый элемент при подписке.

Наконец, я предлагаю использовать RxTest и TestScheduler вместо GCD.

Позвольте мне знать, если это помогает.

person damianesteban    schedule 15.02.2018

rx.text полагается на следующие события UIControlEvents: .allEditingEvents и .valueChanged. Таким образом, явная отправка события onNext не приведет к отправке действия для этого события. Вы должны отправить действие вручную.

sut.nameField.rx.text.onNext(name)
sut.nameField.sendActions(for: .valueChanged)
person JT501    schedule 17.09.2019