Daniel Gomez Rico
Daniel Gomez Rico

Reputation: 15936

JUnit Scheduler that doesn't depend on Android

I´m trying to use MVP to enhance unit testing and run tests faster (because I'm testing logic not android code so I avoid using things like RobotElectric).

But I´m using RXAndroid and it needs Looper to get Schedulers.io() and AndroidSchedulers.mainThread() and when I try to run sometime like

class Phone {
    public Observable<> sendSms(String number){
        //...
    }
}

Phone.getInstance().sendSms(phoneNumber)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(phone -> {
                    mView.dismissProgress();
                    mView.startCodeView(phone);
                }, error -> {
                    mView.dismissProgress();
                    mView.showError(error);
                });

I get:

Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.os.Looper.getMainLooper(Looper.java)
at rx.android.schedulers.AndroidSchedulers.<clinit>(AndroidSchedulers.java:27)
... 28 more

I tried:

android {
  // ...
  testOptions { 
    unitTests.returnDefaultValues = true
  }
}

But it will not work because I want to run full JUnit tests and not Roboelectric or Espresso stuff.

How can I accomplish it? is there any Scheduler that will not crash because of this?

Upvotes: 7

Views: 5015

Answers (4)

Akhil Dad
Akhil Dad

Reputation: 1814

I am also using scheduler thread for this, but in my test SetUp and TearDown.

@Before
public void setUp() throws Exception {
    RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
        @Override
        public Scheduler getMainThreadScheduler() {
            return Schedulers.immediate();
        }
    });
}

@After
public void tearDown() {
    RxAndroidPlugins.getInstance().reset();
}

Will this help?

Upvotes: 22

Daniel Gomez Rico
Daniel Gomez Rico

Reputation: 15936

I ended up adding using transformations and "flavour injection classes" for this, have a class that uses the main for prod/debug releases and use another class in the test flavour folder for testing Schedulers.immediate().

normal flavoured class:

public class Transformer {

  public static <T> Observable.Transformer<T, T> applyIoSchedulers() {
    return observable -> observable.subscribeOn(getIoScheduler())
        .observeOn(getMainScheduler());
  }

  private static Scheduler getIoScheduler() {
    return Schedulers.io();
  }

  private static Scheduler getMainScheduler() {
    return AndroidSchedulers.mainThread();
  }
}

Testing flavoured class:

public class Transformer {

  public static <T> Observable.Transformer<T, T> applyIoSchedulers() {
    return observable -> observable.subscribeOn(getIoScheduler())
        .observeOn(getMainScheduler());
  }

  private static Scheduler getIoScheduler() {
    return Schedulers.immediate() ;
  }

  private static Scheduler getMainScheduler() {
    return Schedulers.immediate() ;
  }
}

Then use it with transformations:

mSessionRepository.login(...)
        .compose(Transformer.applyIoSchedulers())
        .subscribe(session -> { })

Upvotes: 5

FriendlyMikhail
FriendlyMikhail

Reputation: 2927

Yup, no android.jar in junit tests means no Loopers. If you use Dagger you can inject a mock scheduler into tests and a real scheduler into source code. You could also use something like Mockito to mock the Scheduler. Otherwise like @Artem Zinnatullin suggested, Robolectric solves this problem. Robolectric 3 is very easy to setup with Android Studio.

Upvotes: 2

Artem Zinnatullin
Artem Zinnatullin

Reputation: 4447

In our practice, we try to avoid using AndroidSchedulers.mainThread() in Presenter because it's a detail of View implementation. You can do this too.

Though we use Robolectric, so anyway it'll work in our tests.

Upvotes: 3

Related Questions