Reputation: 59
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
Reputation: 10717
I use this technic:
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
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
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