Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 619
Reimond Hill Avatar asked Apr 08 '26 01:04

Reimond Hill


1 Answers

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:

  • Don't make your subject properties var use let instead because you don't want anybody to be able to replace them with unbound versions.

  • The fact that you have to use the AppLocationManager in this code that has no need of it implies that the object is doing too much. There is nothing wrong with having multiple view models in a view controller that each handle different parts of the view.

  • Best would be to avoid using Subjects (Relays) at all in your view model code, if needed, they are better left in the imperative side of the code.

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 == ""}
}
like image 160
Daniel T. Avatar answered Apr 09 '26 23:04

Daniel T.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!