RxSwift unit test observed in ViewController

I am brand new to RxSwift. I have a view controller that has a typeahead / autocomplete function (i.e. custom types in a UITextField, and as soon as they enter at least two characters, a network request is executed to find the corresponding sentences). The controller viewDidLoadcalls the following Observable configuration method:

class TypeaheadResultsViewController: UIViewController {

var searchTextFieldObservable: Observable<String>!
@IBOutlet weak var searchTextField: UITextField!
private let disposeBag = DisposeBag()
var results: [TypeaheadResult]?

override func viewDidLoad() {
    super.viewDidLoad()
    //... unrelated setup stuff ...
    setupSearchTextObserver()
}

func setupSearchTextObserver() {
            searchTextFieldObservable =
                self.searchTextField
                    .rx
                    .text
                    .throttle(0.5, scheduler: MainScheduler.instance)
                    .map { $0 ?? "" }

            searchTextFieldObservable
                .filter { $0.count >= 2 }
                .flatMapLatest { searchTerm in self.search(for: searchTerm) }
                .subscribe(
                    onNext: { [weak self] searchResults in
                        self?.resetResults(results: searchResults)
                    },
                    onError: { [weak self] error in
                        print(error)
                        self?.activityIndicator.stopAnimating()
                    }
                )
                .disposed(by: disposeBag)

            // This is the part I want to test:        
            searchTextFieldObservable
                .filter { $0.count < 2 }
                .subscribe(
                    onNext: { [weak self] _ in
                        self?.results = nil
                    }
                )
                .disposed(by: disposeBag)
    }
}

Everything seems to be working fine, but I'm trying to figure out how the unit test behavior searchTextFieldObservable. To keep it simple, I just want the unit test to confirm that it is resultsset to nilwhen it searchTextFieldhas less than 2 characters after the change event.
I tried several different approaches. Currently my test is as follows:

    class TypeaheadResultsViewControllerTests: XCTestCase {
        var ctrl: TypeaheadResultsViewController!

        override func setUp() {
            super.setUp()
            let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
            ctrl = storyboard.instantiateViewController(withIdentifier: "TypeaheadResultsViewController") as! TypeaheadResultsViewController
        }

        override func tearDown() {
            ctrl = nil
            super.tearDown()
        }

        /// Verify that the searchTextObserver sets the results array
        /// to nil when there are less than two characters in the searchTextView
        func testManualChange() {
          // Given: The view is loaded (this triggers viewDidLoad)
          XCTAssertNotNil(ctrl.view)
          XCTAssertNotNil(ctrl.searchTextField)
          XCTAssertNotNil(ctrl.searchTextFieldObservable)

          // And: results is not empty
          ctrl.results = [ TypeaheadResult(value: "Something") ]

          let tfObservable = ctrl.searchTextField.rx.text.subscribeOn(MainScheduler.instance)
          //ctrl.searchTextField.rx.text.onNext("e")
          ctrl.searchTextField.insertText("e")
          //ctrl.searchTextField.text = "e"
          do {
              guard let result =
                try tfObservable.toBlocking(timeout: 5.0).first() else { 
return }
            XCTAssertEqual(result, "e")  // passes
            XCTAssertNil(ctrl.results)  // fails
        } catch {
            print(error)
        }
    }

, , / searchTextFieldObservable (, , searchTextField), " , :".

+4
2

, self.ctrl.searchTextField.rx.text.onNext("e") searchTextFieldObservable onNext.

, self.ctrl.searchTextField.text = "e".

( ), textField : self.ctrl.searchTextField.insertText("e").

, , UITextField.rx.text UIKeyInput.

0

UIViewControllers . .

, , , - mock textField text, , . . textField.rx.text.bind(viewModel.query) , unit test .

class ViewModel {
   let query: Variable<String?> = Variable(nil)
   let results: Variable<[TypeaheadResult]> = Variable([])

   let disposeBag = DisposeBag()

   init() {
     query
       .asObservable()
       .flatMap { query in 
          return query.count >= 2 ? search(for: $0) : .just([])
       }
       .bind(results)
       .disposed(by: disposeBag)
   }

   func search(query: String) -> Observable<[TypeaheadResult]> {
     // ...
   }
}

:

class TypeaheadResultsViewControllerTests: XCTestCase {

    func testManualChange() {
      let viewModel = ViewModel()
      viewModel.results.value = [/* .., .., .. */]

      // this triggers the subscription, but does not trigger the search
      viewModel.query.value = "1"

      // assert the results list is empty
      XCTAssertEqual(viewModel.results.value, [])
   }
}

, .

, :

  • .
  • textField query (.. textField.rx.text.asDriver().drive(viewModel.query)).
  • (.. viewModel.results.asObservable.subscribe(/* ... */)).

- , .

0

Source: https://habr.com/ru/post/1693805/


All Articles