Rici
Rici

Reputation: 1044

How to test the UI Binding between RxSwift Variable and RxCocoa Observable?

I have a simple ViewModel with one property:

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

And I'm binding it to its UITextField in my 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)
    }
}

Now, I want to Unit Test it.

I'm able to fill the nameField.text property though Rx using .onNext event - nameField.rx.text.onNext("My Name Here").

But the viewModel.name isn't being filled. Why? How can I make this Rx behavior work in my XCTestCase?

Please see below the result of my last test run:

Last Test Run Screenshot

Upvotes: 1

Views: 1982

Answers (2)

JT501
JT501

Reputation: 1695

rx.text relies on the following UIControlEvents: .allEditingEvents and .valueChanged. Thus, explicitly send a onNext events will not send action for these event. You should send action manually.

sut.nameField.rx.text.onNext(name)
sut.nameField.sendActions(for: .valueChanged)

Upvotes: 1

damianesteban
damianesteban

Reputation: 1611

I believe the issue you are having is that the binding is not explicitly scheduled on the main thread. I recommend using a Driver in this case:

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)
}

// ..

With a Driver, the binding will always happen on the main thread and it will not error out.

Also, in your test I would call skip on the binding:

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

because the Driver will replay the first element when it subscribes.

Finally, I suggest using RxTest and a TestScheduler instead of GCD.

Let me know if this helps.

Upvotes: 2

Related Questions