I am facing problem how to write test code for retrofit.The codes are as below.
MainActivity.java
import android.os.Bundle;
import java.util.List;
import gallery.com.brandlistview.Bird;
import retrofit.Callback;
import retrofit.client.Response;
public class MainActivity implements Callback<List<Bird>> {
#Override
protected void onCreate(Bundle savedInstanceState) {
Globals.getRestClient().getBirdClient().getBirdList(this);
}
#Override
public void success(List<Bird> birds, Response response) {
}
#Override
public void failure(--------) {
}
}
Bird.java
public class Bird {
private String image_url;
public String getImage_url() {
return image_url;
}
public void setImage_url(---------) {
-----------
}
}
Globals.java
public class globals{
public static RestClient restClient;
public static RestClient getRestClient() {
return restClient;
}
public static BirdClient getBirdClient() {
return getRestClient().getBirdClient();
}
}
RestClient.java
public class RestClient{
private BirdClient mBirdClient;
public RestClient() {
mBirdClient = new BirdClient(getBirdService());
}
public BirdClient getBirdClient() { return mBirdClient; }
public static BirdService getBirdService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
return restAdapter.create(BirdService.class);
}
}
BirdClient.java
public class BirdClient{
private BirdService mAPIService;
public BirdClient(BirdService service) {
assert service != null;
mAPIService = service;
}
public void getBirdList(final Callback<List<Bird>> delegate) {
----------------------
}
}
BirdService.java
public interface BirdService {
#GET(URL)
void getBirdList(Callback<List<Bird>> objectManager);
}
I want to write test code to test if the number of items in List is equal to 15(for example) after successful callback of getBirdList() as shown is MainActivity.java . Also, I am using Robolectric and Mockito library for testing. I am trying to write since tuesday .please help.
I suggest that you don't test that the actual request to server is working, due to the server response might change and you may not have internet when running test, but test it with some mock data.
Here is an example where he's using mockito together with retrofit to unit-test api functionality:
http://www.mdswanson.com/blog/2013/12/16/reliable-android-http-testing-with-retrofit-and-mockito.html
Good luck!
Related
I know it was asked before, but i am currently diving into testing and i have the struggle to unit test presenter in MVP pattern with Mockito
My code setup:
Item class
public class ItemJSON {
#SerializedName("title")
String textHolder;
#SerializedName("id")
int factNumber;
public ItemJSON(String factText, int factNumber) {
this.textHolder = factText;
this.factNumber = factNumber;
}
//getters and setters
}
Contractor:
public interface Contractor {
interface Presenter {
void getPosts();
}
interface View {
//parse data to recyclerview on Succesfull call.
void parseDataToRecyclerView(List<ItemJSON> listCall);
void onResponseFailure(Throwable throwable);
}
interface Interactor {
interface onGetPostsListener {
void onSuccessGetPostCall(List<ItemJSON> listCall);
void onFailure(Throwable t);
}
void getPosts(onGetPostsListener onGetPostsListener);
}
}
API class:
#GET("posts")
Call<List<ItemJSON>> getPost();
Interactor class:
public class InteractorImpl implements Contractor.Interactor{
#Override
public void getPosts(onGetPostsListener onGetPostsListener) {
// NetworkService responsible for seting up Retrofit2
NetworkService.getInstance().getJSONApi().getPost().enqueue(new Callback<List<ItemJSON>> () {
#Override
public void onResponse(#NonNull Call<List<ItemJSON>> call, #NonNull Response<List<ItemJSON>> response) {
Log.d("OPERATION #GET","CALLBACK SUCCESSFUL");
onGetPostsListener.onSuccessGetPostCall (response.body ());
}
#Override
public void onFailure(#NonNull Call<List<ItemJSON>>call, #NonNull Throwable t) {
Log.d("OPERATION #GET","CALLBACK FAILURE");
onGetPostsListener.onFailure (t);
}
});
}
Presenter class:
public class PresenterImpl implements Contractor.Presenter, Contractor.Interactor.onGetPostsListener {
private final Contractor.View view;
private final Contractor.Interactor interactor;
public PresenterImpl (Contractor.View view,Contractor.Interactor interactor){
this.view = view;
this.interactor = interactor;
}
#Override
public void getPosts() {
interactor.getPosts (this);
}
#Override
public void onSuccessGetPostCall(List<ItemJSON> listCall) {
view.parseDataToRecyclerView (listCall);
}
}
So i try to ran some unit test on presenter, but they constanlty fail and i keep getting next error
Wanted but not invoked Actually, there were zero interactions with this mock
Unit test class:
#RunWith (MockitoJUnitRunner.class)
public class ApiMockTest{
#Mock
Contractor.View view;
private PresenterImpl presenter;
#Captor
ArgumentCaptor<List<ItemJSON>> jsons;
#Before
public void setUp() {
MockitoAnnotations.openMocks (this);
presenter = new PresenterImpl (view,new InteractorImpl ());
}
#Test
public void loadPost() {
presenter.getPosts ();
verify(view).parseDataToRecyclerView (jsons.capture ());
Assert.assertEquals (2, jsons.capture ().size ());
}
}
I try to understand what i am doing wrong and how to fix this issue, but as for now i am ran out of ideas. I will aprecciate any help.
Thanks in the adavance
UPD: in all cases in main activity presenter get called in onClick
Main Activity class:
public class MainActivity extends AppCompatActivity implements Contractor.View {
public Contractor.Presenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
presenter = new PresenterImpl (this,new InteractorImpl ());
binding.getButton.setOnClickListener(view ->presenter.getPosts () );
...//code
#Override
public void parseDataToRecyclerView(List<ItemJSON> listCall) {
adapter.updateList(listCall); //diff call to put data into recyclerview adapter
}
}
}
I ran into this situation also, even using the mockk library. The problem is that your method is an interface method. You need to actually call it from a view which has implemented this interface.
So, I declared my Worker Class NotifyWorker and called it in the MainActivity with the below code :
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.util.Log;
import com.birbit.android.jobqueue.JobManager;
import com.birbit.android.jobqueue.config.Configuration;
import com.birbit.android.jobqueue.log.CustomLogger;
public class MainActivity extends AppCompatActivity {
//some code
#Override
protected void onCreate(Bundle savedInstanceState) {
if(checkForUpdate(this)){
return;
}
//some code
final OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotifyWorker.class)
.setInitialDelay(1, TimeUnit.MINUTES)
.addTag(workTag)
.build();
//WorkManager.getInstance(getBaseContext()).beginUniqueWork(workTag, ExistingWorkPolicy.REPLACE, notificationWork);
WorkManager.getInstance(getApplication()).enqueue(notificationWork);
Log.v(TAG,"Worker executed");
//Set content
setContentView(R.layout.activity_main);
//some code
}
}
So, I got the following error on running the app:
Caused by: java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
So, I followed The developer documentation , added the required code to my manifest, But i am unable to add the following method in myApplication() class :
class MyApplication extends Application implements Configuration.Provider {
#Override
public Configuration getWorkManagerConfiguration() {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
}
}
My application class looks like below :
public class xxxApplication extends Application implements androidx.work.Configuration.Provider {
private static xxxApplication instance;
private JobManager jobManager;
private BroadcastReceiver broadcastReceiver;
public xxxApplication(){
instance = this;
}
#Override
public void onCreate(){
super.onCreate();
//ensure jobmanager is configured
getJobManager();
broadcastReceiver=new NetworkChangeReceiver();
IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(broadcastReceiver, intentFilter);
}
#Override
public void onTerminate() {
super.onTerminate();
unregisterReceiver(broadcastReceiver);
}
private void configureJobManager(){
Configuration.Builder builder = new Configuration.Builder(this)
.customLogger(new CustomLogger() {
private static final String TAG = "JOBS";
#Override
public boolean isDebugEnabled() {
return true;
}
#Override
public void d(String text, Object... args) {
Log.d(TAG, String.format(text, args));
}
#Override
public void e(Throwable t, String text, Object... args) {
Log.e(TAG, String.format(text, args), t);
}
#Override
public void e(String text, Object... args) {
Log.e(TAG, String.format(text, args));
}
#Override
public void v(String text, Object... args) {
}
})
.minConsumerCount(1)
.maxConsumerCount(3)
.loadFactor(3)
.consumerKeepAlive(120);
//configure here if job scheduler involved
jobManager = new JobManager(builder.build());
}
public synchronized JobManager getJobManager() {
if (jobManager == null) {
configureJobManager();
}
return jobManager;
}
public static xxxApplication getInstance() {
return instance;
}
#Override
public **Configuration** getWorkManagerConfiguration() { //I get some conflict here , resolved by using androidx.work.Configuration as a return type
return Configuration.Builder() // Can't resolve the issue ?? what do i write here??
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
}
}
I have commented the part where i get an issue , syntax error says "Method call expected" . How do I resolve this issue?? Why is it not working as expected? Is there a conflict between JobManager and WorkManager configurations?
UPDATE : So,there was actually a conflict between the configuration files of jobmanager and workmanager .Had to call the later using full location definition.
I am answering this myself, in case it is useful for others who have used Job-Schedulers before, and are now adding background processes using WorkManager .
While adding the extension as specified in the Developer documentation for custom initialization , use the following code in case you have already used JobScheduler in your application class :
public class xxxApplication extends Application implements androidx.work.Configuration.Provider {
//OTHER CODE
#Override
public androidx.work.Configuration getWorkManagerConfiguration() {
return new androidx.work.Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
}
}
However, it is recommended that you migrate all the processes to WorkManager to avoid further conflicts.
In Glide 3.x we could add register a String model loader like following:
public class GlideService /* implements GlideModule*/ {
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(String.class, InputStream.class, new HeaderedLoader.Factory());
}
private static class HeaderedLoader extends BaseGlideUrlLoader<String> {
public HeaderedLoader(Context context) {
super(context);
}
#Override
protected String getUrl(String model, int width, int height) {
return model;
}
#Override
protected Headers getHeaders(String model, int width, int height) {
LazyHeaders.Builder headersBuilder = new LazyHeaders.Builder();
if (BuildConfig.FLAVOR.equals("staging")) {
String auth = "username:password";
String base64 = Base64.encodeToString(auth.getBytes(), Base64.NO_WRAP);
headersBuilder.addHeader("Authorization", "Basic " + base64);
}
return headersBuilder.build();
}
public static class Factory implements ModelLoaderFactory<String, InputStream> {
#Override
public StreamModelLoader<String> build(Context context, GenericLoaderFactory factories) {
return new HeaderedLoader(context);
}
#Override
public void teardown() { }
}
}
}
But it's not clear from the docs how we can accomplish this for the lastest version.
I face the same issue as you.
Here is how i fixed it.
Your GlideService needs to extends AppGlideModule (if you are writing code for an application). For more details about this see glide documentation
As you are now using glide 4.x, your class HeaderedLoader now has overriden the two default constructor of BaseGlideUrlLoader<String>, these are protected HeaderedLoader(ModelLoader<GlideUrl, InputStream> concreteLoader) and protected HeaderedLoader(ModelLoader<GlideUrl, InputStream> concreteLoader, #Nullable ModelCache<String, GlideUrl> modelCache).
Create a factory for the HeaderedLoader class.
This should look like
static class Factory implements ModelLoaderFactory<String, InputStream> {
#Override
public ModelLoader<String, InputStream> build(MultiModelLoaderFactory multiFactory) {
ModelLoader<GlideUrl, InputStream> loader = multiFactory.build(GlideUrl.class, InputStream.class);
return new HeaderedLoader(loader);
}
#Override public void teardown() { /* nothing to free */ }
}
You will then override the public void registerComponents(Context context, Glide glide, Registry registry) method of glide module super class with the following
#Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(String.class, InputStream.class, new HeaderedLoader.Factory());
}
You should be able to keep your logic for handling http header inside the HeaderedLoader class.
I have use this approach to add custom header to glide request with glide 4.0.
try this
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
/**
* A simple model loader for fetching media over http/https using OkHttp.
*/
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
public OkHttpUrlLoader(Call.Factory client) {
this.client = client;
}
#Override
public boolean handles(GlideUrl url) {
return true;
}
#Override
public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
Options options) {
return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
}
/**
* The default factory for {#link OkHttpUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* #param client this is typically an instance of {#code OkHttpClient}.
*/
public Factory(Call.Factory client) {
this.client = client;
}
#Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
#Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}`
Here's my code, which I based on some old tutorial found on the internet. There really should be some examples on the main site of Dagger 2, I found it really difficult to understand how to implement all this.
It's really a lot of work to get such a simple app to run. I have two questions:
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Also how can I make the scope of the Logger class a singleton? Right now every button click creates a new logger instance.
Probably I dont understand some underlying concepts, I've only used dependency injection in Spring before and all of this seems strange to me.
public class MainActivity extends AppCompatActivity {
private Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.getLogger().log("Hello!",MainActivity.this);
}
});
}
}
public class Logger {
private static int i = 0;
public Logger(){
i++;
}
public static int getI() {
return i;
}
public void log(String text, Context context){
Toast.makeText(context,text+" "+i,Toast.LENGTH_SHORT).show();
}
}
#Singleton
#Component(modules={LoggerModule.class})
public interface LoggerComponent {
Logger getLogger();
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
The answer is
public class MainActivity extends AppCompatActivity {
#OnClick(R.id.button) //ButterKnife
public void onClickButton() {
logger.log("Hello!");
}
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Injector.INSTANCE.getApplicationComponent().inject(this);
ButterKnife.bind(this);
}
#Override
protected void onDestroy() {
ButterKnife.unbind(this);
super.onDestroy();
}
}
public class Logger {
private static int i = 0;
private CustomApplication customApplication;
public Logger(CustomApplication application) {
this.customApplication = application;
i++;
}
public static int getI() {
return i;
}
public void log(String text){
Toast.makeText(customApplication, text + " " + i,Toast.LENGTH_SHORT).show();
}
}
public interface LoggerComponent {
Logger logger();
}
#Module
public class ApplicationModule {
private CustomApplication customApplication;
public ApplicationModule(CustomApplication customApplication) {
this.customApplication = customApplication;
}
#Provides
public CustomApplication customApplication() {
return customApplication;
}
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
#Singleton
#Component(modules={LoggerModule.class, ApplicationModule.class})
public interface ApplicationComponent extends LoggerComponent {
CustomApplication customApplication();
void inject(MainActivity mainActivity);
}
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Injector.INSTANCE.initializeApplicationComponent(this);
}
}
public enum Injector {
INSTANCE;
private ApplicationComponent applicationComponent;
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
void initializeApplicationComponent(CustomApplication customApplication) {
this.applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(customApplication))
.build();
}
}
This is currently our Dagger2 architecture.
EDIT: This is from our actual code for Retrofit stuff from our application we're making:
public interface RecordingService {
ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException;
}
public class RecordingServiceImpl
implements RecordingService {
private static final String TAG = RecordingServiceImpl.class.getSimpleName();
private RetrofitRecordingService retrofitRecordingService;
public RecordingServiceImpl(RetrofitRecordingService retrofitRecordingService) {
this.retrofitRecordingService = retrofitRecordingService;
}
#Override
public ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException {
try {
return retrofitRecordingService.getScheduledPrograms(String.valueOf(userId));
} catch(RetrofitError retrofitError) {
Log.e(TAG, "Error occurred in downloading XML file.", retrofitError);
throw new ServerErrorException(retrofitError);
}
}
}
#Module
public class NetworkClientModule {
#Provides
#Singleton
public OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new HeaderInterceptor());
return okHttpClient;
}
}
#Module(includes = {NetworkClientModule.class})
public class ServiceModule {
#Provides
#Singleton
public RecordingService recordingService(OkHttpClient okHttpClient, Persister persister, AppConfig appConfig) {
return new RecordingServiceImpl(
new RestAdapter.Builder().setEndpoint(appConfig.getServerEndpoint())
.setConverter(new SimpleXMLConverter(persister))
.setClient(new OkClient(okHttpClient))
.setLogLevel(RestAdapter.LogLevel.NONE)
.build()
.create(RetrofitRecordingService.class));
}
//...
}
public interface RetrofitRecordingService {
#GET("/getScheduledPrograms")
ScheduledRecordsXML getScheduledPrograms(#Query("UserID") String userId);
}
public interface ServiceComponent {
RecordingService RecordingService();
//...
}
public interface AppDomainComponent
extends InteractorComponent, ServiceComponent, ManagerComponent, ParserComponent {
}
#Singleton
#Component(modules = {
//...
InteractorModule.class,
ManagerModule.class,
ServiceModule.class,
ParserModule.class
//...
})
public interface ApplicationComponent
extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
void inject(DashboardActivity dashboardActivity);
//...
}
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Yes for all classes that created by the system like Application, Activity and Service. but for you own classes, you don't need that. just annotate you constructor with #inject and dagger will provide your dependencies.
Also how can I make the scope of the Logger class a singleton? Right
now every button click creates a new logger instance.
Your setup for singleton is correct. but you have to initialize the component one time after the activity is created (onCreate) in order to let dagger to inject all fields. Also you can utilize lazy injection feature if you don't need the Logger object right away.
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.inject(this);
init();
}
Then you can access your object without take the reference from the component:
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
logger.log("Hello!",MainActivity.this);
}
});
}
In summary:
You have to initialize the component in all classes that use field injections.
UPDATE:
To do the actual injection, you have to declare inject() method into your component and dagger will automatically implement it. This method will take care of provide any object annotated with #Inject.
I'm developing application based on MVP pattern using retrofit to perform networking. I want to unit test my presenter but it fails.
In my app dataView implements DataView which is mocked by Mockito. In
DataPresenter in onViewCreated method MyApi instance is get from MyApplication and it performs request. Anonymous Subscriber<Data> onNext calls showData(Data data) on dataView. Unfortunatelly Mockito.verify(dataView).showData(data) fails the test. I mocked retrofit client by my self to response in deterministic way.
Code below:
public class DataFragment extends ProgressFragment implements DataView {
protected DataPresenter mDataPresenter;
//[...] initialization arguments boilerplate etc.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDataPresenter.onViewCreated(mId);
//[...]
}
#Override
public void startLoading() {
setContentShown(false);
}
#Override
public void stopLoading() {
setContentShown(true);
}
#Override
public void showData(Data data) {
setContentEmpty(false);
//[...] present data
}
#Override
public void showError() {
setContentEmpty(true);
setEmptyText(R.string.unknown_error);
}
}
In DataPresenter:
#Override
public void onViewCreated(long id) {
getView().startLoading();
MyApplication.getInstance().getMyApi().checkIn(User.getUser().getFormattedTokenForRequest(),
(int) id).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new Subscriber<Data>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
getView().showError();
getView().stopLoading();
}
#Override
public void onNext(Data data) {
getView().showData(data);
getView().stopLoading();
}
});
;
}
My test case:
public static final String GOOD_RESPONSE = "[Data in JSON]"
public static final int GOOD_STATUS = 201;
#Mock
DataView mDataView;
#Mock
MyApplication app;
#Mock
SharedPreferencesManager mSharedPreferencesManager;
DataPresenter mDataPresenter;
#Before
public void setUp() throws Exception {
mDataPresenter = new DataPresenterImpl(mDataView);
MyApplication.setInstance(app);
Mockito.when(app.getSharedPreferencesManager()).thenReturn(mSharedPreferencesManager);
Mockito.when(mSharedPreferencesManager.getUser()).thenReturn(null);
}
#Test
public void testCase() throws Exception {
RestAdapter adapter = (new RestAdapter.Builder()).setEndpoint(URL)
.setClient(new MockClient(GOOD_RESPONSE, GOOD_STATUS))
.build();
Mockito.when(app.getMyApi()).thenReturn(adapter.create(MyApi.class));
mCheckInPresenter.onViewCreated(3);
Mockito.verify(checkInView).startLoading();
Mockito.verify(checkInView).showData(new Data());
}
Test fails on "Wanted but not invoked:
dataView.showData(..." .
What is interesting Response execute() is called in MockClient but onNext(Data data) in subscriber included in DataPresenterImpl is not. Any ideas? I guess it is a problem with request being asynchronous.
The problem is that the work is being sent to a different thread and mockito cant verify whats going on. My solution to this would be to create a scheduler factory and mock it out and return the main thread for tests
like these. Something like:
public class schedulerFactory {
public Scheduler io() {
return Schedulers.io();
}
//etc
}
then in your test you would write something like this:
#Mock SchedulerFactory factory
#Before
public void setUp() {
when(factory.io()).thenReturn(Schedulers.mainThread());
}
in general its a good idea to run all the code in the same thread for testing