Rxswift: How to change Schedulers instance for unit tests

Created on 14 Feb 2017  路  7Comments  路  Source: ReactiveX/RxSwift

Hi

I want to unit test my presenter like:

func test_onLogin_showMainView() {
        //when(loginRepository.login).return(sessionObject)
        presenter.onLoginButtonClick("[email protected]", "b")
        //verify(coordinator).openMainView(sessionObject)
}

But It will not work since login button is async, check:

func onLoginButtonClick(username: String?, pwd: String?) {
        view?.showProgressDialog()

        userRepository.login(username: username!, password: pwd!)
            .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))   // <------
            .observeOn(MainScheduler.instance)    // <------
            .subscribe(
                onNext: { session in
                    self.view?.dismissProgressDialog()
                    self.coordinator.openMainView(session: session)
            },
                onError: {
                    self.view?.dismissProgressDialog()
                    self.view?.show(error: $0)
            })
            .addDisposableTo(disposeBag)
    }

In RxJava you can override which Scheduler MainScheduler.instance returns for example, so you run before every test:

Func1<Scheduler, Scheduler> immediateScheduler = new Func1<Scheduler, Scheduler>() {
      @Override
      public Scheduler call(Scheduler scheduler) {
        return Schedulers.immediate;
      }
    };

RxJavaHooks.setOnIOScheduler(immediateScheduler);
RxJavaHooks.setOnComputationScheduler(immediateScheduler);
RxJavaHooks.setOnNewThreadScheduler(immediateScheduler);

Then everytime my presenter try to set an io, computation or another scheduler it will use the Schedulers.immediate.

How can I do this with rxswift?

Most helpful comment

Hi @caipivara ,

I think this comes down to declaring a static variable and just using it.

public var myDefaultScheduler: SchedulerType = MainScheduler.instance 
public var myWorkScheduler: SchedulerType = ConcurrentDispatchQueueScheduler(qos: .default)

extension ObservableType {

    /**
     Makes the observable Subscribe to io thread and Observe on main thread
     */
    public func composeIoToMainThreads() -> Observable<E> {
        return self.subscribeOn(myWorkScheduler)
            .observeOn(myDefaultScheduler)
    }

}

Is there some difference?

All 7 comments

I do this in my tests using dependency injection. Many of my init() methods look like this:

init(scheduler: SchedulerType = MainScheduler.instance)

In my production code, I build instances using just init(), but in my unit tests, I can do init(scheduler: testScheduler).

Jesse Squires did a good blog post about this technique, which applies to more than just RxSwift: http://www.jessesquires.com/refactoring-singletons-in-swift/

Hi @caipivara ,

You can use what @dpassage has suggested for RxSwift. We don't have any static dependencies there.

The only similar API we have is in RxCocoa for Driver "unit" and it's called driveOnScheduler.

@kzaher @dpassage that sounds good but I will not be able to use this extension (idea taken from http://blog.danlew.net/2015/03/02/dont-break-the-chain/)

extension ObservableType {

    /**
     Makes the observable Subscribe to io thread and Observe on main thread
     */
    public func composeIoToMainThreads() -> Observable<E> {
        return self.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
            .observeOn(MainScheduler.instance)
    }

}

Hi @caipivara ,

I think this comes down to declaring a static variable and just using it.

public var myDefaultScheduler: SchedulerType = MainScheduler.instance 
public var myWorkScheduler: SchedulerType = ConcurrentDispatchQueueScheduler(qos: .default)

extension ObservableType {

    /**
     Makes the observable Subscribe to io thread and Observe on main thread
     */
    public func composeIoToMainThreads() -> Observable<E> {
        return self.subscribeOn(myWorkScheduler)
            .observeOn(myDefaultScheduler)
    }

}

Is there some difference?

@kzaher I will use that ;) thanks

Then how do you do assertions after changing the scheduler?

@hlung you need to set the observable to observeOn and subscribeOn to MainScheduler.instance on tests somehow, via a Factory or something. If the RX Shift schedules use the main thread they don't go to another thread when the Rx things are been emited and handled, and so it runs synchronously and should be safe for asserting after.

Was this page helpful?
0 / 5 - 0 ratings