unit-testingrx-swiftrxtest

Testing ViewModel in RxSwift


I would like to perform a test in one of my ViewModels that contains a BehaviorRelay object called "nearByCity" that it is bind to BehaviorRelay called "isNearBy". That's how my view model looks like.

class SearchViewViewModel: NSObject {

    //MARK:- Properties
    //MARK: Constants
    let disposeBag = DisposeBag()


    //MARK: Vars
    var nearByCity:BehaviorRelay<String?> = BehaviorRelay(value: nil)
    var isNearBy = BehaviorRelay(value: true)        

    //MARK:- Constructor
    init() {

        super.init()
        setupBinders()

    }

}


//MARK:- Private methods
private extension SearchViewViewModel{

    func setupBinders(){

        nearByCity
            .asObservable()
            .distinctUntilChanged()
            .map({$0 ?? ""})
            .map({$0 == ""})
            .bind(to: isNearBy)
            .disposed(by: disposeBag)

    }

}

The test that i want to perform is to actually verify that when the string is accepted, the bool value also changes according to the function setupBinders().

Any Idea?

Thank you


Solution

  • Here's one way to test:

    class RxSandboxTests: XCTestCase {
    
        func testBinders() {
            let scheduler = TestScheduler(initialClock: 0)
            let source = scheduler.createColdObservable([.next(5, "hello"), .completed(10)])
            let sink = scheduler.createObserver(Bool.self)
            let disposeBag = DisposeBag()
    
            let viewModel = SearchViewViewModel(appLocationManager: StubManager())
            source.bind(to: viewModel.nearByCity).disposed(by: disposeBag)
            viewModel.isNearBy.bind(to: sink).disposed(by: disposeBag)
    
            scheduler.start()
    
            XCTAssertEqual(sink.events, [.next(0, true), .next(5, false)])
        }
    }
    

    Some other points:

    At minimum, break up your setupBinders function so that the parts are independently testable. Your above could have been written as a simple, easily tested, free function:

    func isNearBy(city: Observable<String?>) -> Observable<Bool> {
        return city
            .distinctUntilChanged()
            .map {$0 ?? ""}
            .map {$0 == ""}
    }