SimpleMan
SimpleMan

Reputation: 59

Unit Testing with retrofit and Rx android

I am new in unit testing and I am unit testing retrofit with rx android. I have one observable which is getting access token from the api and I use it using retrofit to send request. I am getting Null Pointer Exception due to it here is my code:

@RunWith(MockitoJUnitRunner.class)
public class AuthenticationTokenGetterTest {
    @Mock
    AuthenticatorInterface authenticatorservice;
    @InjectMocks
    AuthenticationTokenGetter tokengetter;

    @Test
    public void testtokkengetter() {
        when(authenticatorservice.servicecall(anyString(), anyString())).thenReturn(
                Observable.just("44fffffggggggg"));

        Observable<String> obs = tokengetter.getToken();
        TestSubscriber<String> testsubscriber = new TestSubscriber<>();
        obs.subscribe(testsubscriber);
        testsubscriber.assertNoErrors(); // Here I get exception
        List<String> value = testsubscriber.getOnNextEvents();
    }

}

But I am getting java.lang.NullPointerException all the time. and my observable code is which I am testing:

@CheckResult
    public Observable<String> getToken() {
        return service.servicecall(key, code)
                .subscribeOn(Schedulers.newThread())
                .doOnNext(new Action1<String>() {
                    public void call(String token) {
                        savedToken = token;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread());
    }

My Error is:

java.lang.AssertionError: Unexpected onError events: 1

    at rx.observers.TestSubscriber.assertNoErrors(TestSubscriber.java:308)
    at AuthenticationTokenGetterTest.testtokkengetter(AuthenticationTokenGetterTest.java:47)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.NullPointerException
    at rx.android.schedulers.LooperScheduler$HandlerWorker.schedule(LooperScheduler.java:77)
    at rx.android.schedulers.LooperScheduler$HandlerWorker.schedule(LooperScheduler.java:91)
    at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.schedule(OperatorObserveOn.java:190)
    at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$1.request(OperatorObserveOn.java:147)
    at rx.Subscriber.setProducer(Subscriber.java:209)
    at rx.Subscriber.setProducer(Subscriber.java:205)
    at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.init(OperatorObserveOn.java:141)
    at rx.internal.operators.OperatorObserveOn.call(OperatorObserveOn.java:75)
    at rx.internal.operators.OperatorObserveOn.call(OperatorObserveOn.java:40)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:46)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.Observable.subscribe(Observable.java:8759)
    at rx.Observable.subscribe(Observable.java:8726)
.AuthenticationTokenGetterTest.testtokkengetter(AuthenticationTokenGetterTest.java:45)

Upvotes: 3

Views: 2235

Answers (2)

Cabezas
Cabezas

Reputation: 10717

I use this technic:

  1. BaseSchedulerProvider (Father)
  2. ImmediateSchedulerProvider (Testing)
  3. SchedulerProvider (Application)

BaseSchedulerProvider :

public interface BaseSchedulerProvider {

    @NonNull
    Scheduler computation();

    @NonNull
    Scheduler io();

    @NonNull
    Scheduler ui();
}

ImmediateSchedulerProvider I use for a test:

public class ImmediateSchedulerProvider implements BaseSchedulerProvider {

    @NonNull
    @Override
    public Scheduler computation() {
        return Schedulers.immediate();
    }

    @NonNull
    @Override
    public Scheduler io() {
        return Schedulers.immediate();
    }

    @NonNull
    @Override
    public Scheduler ui() {
        return Schedulers.immediate();
    }
}

And SchedulerProvider I use in my Presenter

public class SchedulerProvider implements BaseSchedulerProvider {

    // Prevent direct instantiation.
    public SchedulerProvider() {
    }

    @Override
    @NonNull
    public Scheduler computation() {
        return Schedulers.computation();
    }

    @Override
    @NonNull
    public Scheduler io() {
        return Schedulers.io();
    }

    @Override
    @NonNull
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }
}

In my PresenterTest I setUp like this:

public class TopicPresenterTest {

            @Mock
            private RemoteDataSource mRemoteDataSource;

            @Mock
            private TopicContract.View mView;

            private BaseSchedulerProvider mSchedulerProvider;

            TopicPresenter mPresenter;

            List<Topics> mList;

            @Before
            public void setup() {
            MockitoAnnotations.initMocks(this);

                    Topics topics = new Topics(1, "Discern The Beach");
                    Topics topicsTwo = new Topics(2, "Discern The Football Player");
                    mList = new ArrayList<>();
                    mList.add(topics);
                    mList.add(topicsTwo);
//ADD IMMEDIATESCHEDULERPROVIDER !!!!!!!!!!!!!!!
                    mSchedulerProvider = new 
                    ImmediateSchedulerProvider();

                    mPresenter = new TopicPresenter(mRemoteDataSource, mView, mSchedulerProvider);

            }

            @Test
            public void fetchData() {

                when(mRemoteDataSource.getTopicsRx())
                        .thenReturn(rx.Observable.just(mList));

                mThemePresenter.fetch();

                InOrder inOrder = Mockito.inOrder(mView);
                inOrder.verify(mView).setLoadingIndicator(false);
                inOrder.verify(mView).showTopics(mList);

            }

}

And In my Presenter

public class TopicPresenter {

        @NonNull
        private BaseSchedulerProvider mSchedulerProvider;

        public TopicPresenter(@NonNull RemoteDataSource remoteDataSource, @NonNull TopicContract.View view) {
                    this.mRemoteDataSource = checkNotNull(remoteDataSource, "remoteDataSource");
                    this.mView = checkNotNull(view, "view cannot be null!");
                    this.mSchedulerProvider = new SchedulerProvider();
 //ADD COMPOSITESUBSCRITPTION !!!!!!     
                    mSubscriptions = new CompositeSubscription();

                    mView.setPresenter(this);
        }
}

You can check my complete example in GitHub and this article.

Upvotes: 0

Than
Than

Reputation: 2769

Your are using AndroidSchedulers.mainThread() which is dependent on Android Looper class, thats why null pointer. Don't create unit tests that are using multiple threads, execute everything on same thread!

You can solve this by doing scheduler injection. Your AuthenticationTokenGetter class should obtain mainThreadScheduler instance by Scheduler reference passed in constructor, so in your normal code you should create your object with mainThreadScheduler and during test create your object with Scheduler implementation that executes everything synchronously.

You can also use RxJava/RxAndroidSchedulersHook for overriding schedulers.

@edit Some articles explaining how to inject/ovveride schedulers:

https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212

https://medium.com/@peter.tackage/an-alternative-to-rxandroidplugins-and-rxjavaplugins-scheduler-injection-9831bbc3dfaf

Both of these solutions has it's advantages and disadvantages, even though these articles are aimed at RxJava2 this approach is still valid with RxJava1 (however scheduler hooks/plugins works a little bit differently in Rx2)

Upvotes: 3

Related Questions