RoboGuice with Standard Android JUnit test cases - android

I want to use RoboGuice in a standard Android JUnit instrumentation test case and override one piece of my app's actual wiring with a mock for testing. I can't find anything online that explains how to do this as all of my search results go to Robolectric with RoboGuoice. I am not using Robolectric nor can I use it in my app for various reasons. Has anyone wired an app with RoboGuice and injected mocks for standard Android Intrumentation test cases?

I'm using the Roboguice 3 and I solved this problem with the following setup and teardown methods within the standard ActivityInstrumentationTestCase2.
Obviously you would need to replace new TestModule() in the snippet below with your own test module class.
#Override
protected void setUp() throws Exception {
super.setUp();
Application app = (Application)getInstrumentation().getTargetContext()
.getApplicationContext();
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app))
.with(new TestModule()));
getActivity();
}
#Override
protected void tearDown() throws Exception {
RoboGuice.Util.reset();
super.tearDown();
}

I've managed to get it work in a simple usage way, you just bind dependencies inside rule using builder and may forget about them later, it will do everything by itself. You may think it's over engineered, but it's realy good for reusing if tyou have a many test classes with robo guice dependencies inside.
Usage in test classes looks like:
#Rule
public InjectWithMocksRule injectWithMocksRule = new InjectWithMocksRule(
this,
() -> new InjectRule
.BindingBuilder()
.add(MyClass.class, mockedClassImpl)
.add(SomeInterface.class, mockedInterfaceImpl));
I wrote helper class TestBindingModule:
public class TestBindingModule extends AbstractModule {
private HashMap<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();
#Override
#SuppressWarnings("unchecked")
protected void configure() {
Set<Entry<Class<?>, Object>> entries = bindings.entrySet();
for (Entry<Class<?>, Object> entry : entries) {
bind((Class<Object>) entry.getKey()).toInstance(entry.getValue());
}
}
public void addBinding(Class<?> type, Object object) {
bindings.put(type, object);
}
public void addBindings(HashMap<Class<?>, Object> bindings) {
this.bindings.putAll(bindings);
}
public static void setUp(Object testObject, TestBindingModule module) {
Module roboGuiceModule = RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application);
Module testModule = Modules.override(roboGuiceModule).with(module);
RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, testModule);
RoboInjector injector = RoboGuice.getInjector(RuntimeEnvironment.application);
injector.injectMembers(testObject);
}
public static void tearDown() {
Application app = RuntimeEnvironment.application;
DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
}
}
Than I use custom Rule to make it work easy:
public class InjectRule implements TestRule {
public interface BindingBuilderFactory {
BindingBuilder create();
}
public static class BindingBuilder {
private HashMap<Class<?>, Object> bindings = new HashMap<>();
public BindingBuilder add(Class<?> dependencyClass, Object implementation) {
bindings.put(dependencyClass, implementation);
return this;
}
HashMap<Class<?>, Object> buildBindings() {
return this.bindings;
}
}
private Object target;
private BindingBuilderFactory bindingBuilderFactory;
public InjectRule(Object target, BindingBuilderFactory bindingBuilderFactory) {
this.target = target;
this.bindingBuilderFactory = bindingBuilderFactory;
}
private void overrideTestInjections(Object target) {
TestBindingModule module = new TestBindingModule();
module.addBindings(this.bindingBuilderFactory.create().buildBindings());
TestBindingModule.setUp(target, module);
}
#Override
public Statement apply(Statement base, Description description) {
return new StatementDecorator(base);
}
private class StatementDecorator extends Statement {
private Statement baseStatement;
StatementDecorator(Statement b) {
baseStatement = b;
}
#Override
public void evaluate() throws Throwable {
before();
try {
baseStatement.evaluate();
} catch (Error e) {
throw e;
} finally {
after();
}
}
void after() {
TestBindingModule.tearDown();
}
void before() {
overrideTestInjections(target);
}
}
}
Also you may want to init mocks with #Mock annotation inside of your test classes, so you need another custom rule:
public class MockitoInitializerRule implements TestRule {
private Object target;
public MockitoInitializerRule(Object target) {
this.target = target;
}
#Override
public Statement apply(Statement base, Description description) {
return new MockitoInitializationStatement(base, target);
}
private class MockitoInitializationStatement extends Statement {
private final Statement base;
private Object test;
MockitoInitializationStatement(Statement base, Object test) {
this.base = base;
this.test = test;
}
#Override
public void evaluate() throws Throwable {
MockitoAnnotations.initMocks(test);
base.evaluate();
}
}
}
And, finaly, you want to combine them to mock mocks first and then set them as dependencies:
public class InjectWithMocksRule implements TestRule {
private final RuleChain delegate;
public InjectWithMocksRule(Object target, InjectRule.BindingBuilderFactory bindingBuilderFactory) {
delegate = RuleChain
.outerRule(new MockitoInitializerRule(target))
.around(new InjectRule(target, bindingBuilderFactory));
}
#Override
public Statement apply(Statement base, Description description) {
return delegate.apply(base, description);
}
}

Related

Unittesting when using Android Annotations, Mockito and MVP pattern

I've up until yesterday successfully put together a very readable Android project using the MVP-pattern and the Android Annotations library.
But yesterday when I started writing unittest for my LoginPresenter a problem has shown itself.
First some code from my LoginPresenter.
...
#EBean
public class LoginPresenterImpl implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
#RootContext
protected LoginActivity loginView;
#Bean(LoginInteractorImpl.class)
LoginInteractor loginInteractor;
#Override public void validateCredentials(String username, String password) {
if (loginView != null) {
loginView.showProgress();
}
if (TextUtils.isEmpty(username)) {
// Check that username isn't empty
onUsernameError();
}
if (TextUtils.isEmpty(password)){
// Check that password isn't empty
onPasswordError();
// No reason to continue to do login
} else {
}
}
#UiThread(propagation = UiThread.Propagation.REUSE)
#Override public void onUsernameError() {
if (loginView != null) {
loginView.setUsernameError();
loginView.hideProgress();
}
}
...
My test:
#RunWith(MockitoJUnitRunner.class)
public class LoginPresenterImplTest {
private LoginPresenter loginPresenter;
#Mock
private LoginPresenter.View loginView;
#Before
public void setUp() {
// mock or create a Context object
Context context = new MockContext();
loginPresenter = LoginPresenterImpl_.getInstance_(context);
MockitoAnnotations.initMocks(this);
}
#After
public void tearDown() throws Exception {
loginPresenter = null;
}
#Test
public void whenUserNameIsEmptyShowUsernameError() throws Exception {
loginPresenter.validateCredentials("", "testtest");
// verify(loginPresenter).onUsernameError();
verify(loginView).setUsernameError();
}
}
The problem is I've not used the standard approach of using MVP-pattern but instead trying out Android Annotations to make the code more readable. So I've not used attachView()- or detachView()-methods for attaching my presenter to my LoginActivity (view). This means that I can't mock my "view". Does someone know a workaround for this problem. I keep getting following message when running the test:
Wanted but not invoked:
loginView.setUsernameError();
-> at com.conhea.smartgfr.login.LoginPresenterImplTest.whenUserNameIsEmptyShowUsernameError(LoginPresenterImplTest.java:48)
Actually, there were zero interactions with this mock.
Solution (I'm not using #RootContext anymore):
Presenter:
#EBean
public class LoginPresenterImpl extends AbstractPresenter<LoginPresenter.View>
implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
private static final String TAG = LoginPresenterImpl.class.getSimpleName();
#StringRes(R.string.activity_login_authenticating)
String mAuthenticatingString;
#StringRes(R.string.activity_login_aborting)
String mAbortingString;
#StringRes(R.string.activity_login_invalid_login)
String mInvalidCredentialsString;
#StringRes(R.string.activity_login_aborted)
String mAbortedString;
#Inject
LoginInteractor mLoginInteractor;
#Override
protected void initializeDagger() {
Log.d(TAG, "Initializing Dagger injection");
Log.d(TAG, "Application is :" + getApp().getClass().getSimpleName());
Log.d(TAG, "Component is: " + getApp().getComponent().getClass().getSimpleName());
Log.d(TAG, "UserRepo is: " + getApp().getComponent().userRepository().toString());
mLoginInteractor = getApp().getComponent().loginInteractor();
Log.d(TAG, "LoginInteractor is: " + mLoginInteractor.getClass().getSimpleName());
}
#Override
public void validateCredentials(String username, String password) {
boolean error = false;
if (!isConnected()) {
noNetworkFailure();
error = true;
}
if (TextUtils.isEmpty(username.trim())) {
// Check that username isn't empty
onUsernameError();
error = true;
}
if (TextUtils.isEmpty(password.trim())) {
// Check that password isn't empty
onPasswordError();
error = true;
}
if (!error) {
getView().showProgress(mAuthenticatingString);
mLoginInteractor.login(username, password, this);
}
}
...
My tests (some of them):
#RunWith(AppRobolectricRunner.class)
#Config(constants = BuildConfig.class)
public class LoginPresenterImplTest {
#Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
private LoginPresenterImpl_ mLoginPresenter;
#Mock
private LoginPresenter.View mLoginViewMock;
#Mock
private LoginInteractor mLoginInteractorMock;
#Captor
private ArgumentCaptor<LoginInteractor.OnLoginFinishedListener> mCaptor;
#Before
public void setUp() {
mLoginPresenter = LoginPresenterImpl_.getInstance_(RuntimeEnvironment.application);
mLoginPresenter.attachView(mLoginViewMock);
mLoginPresenter.mLoginInteractor = mLoginInteractorMock;
}
#After
public void tearDown() throws Exception {
mLoginPresenter.detachView();
mLoginPresenter = null;
}
#Test
public void whenUsernameAndPasswordIsValid_shouldLogin() throws Exception {
String authToken = "Success";
mLoginPresenter.validateCredentials("test", "testtest");
verify(mLoginInteractorMock, times(1)).login(
anyString(),
anyString(),
mCaptor.capture());
mCaptor.getValue().onSuccess(authToken);
verify(mLoginViewMock, times(1)).loginSuccess(authToken);
verify(mLoginViewMock, times(1)).hideProgress();
}
#Test
public void whenUsernameIsEmpty_shouldShowUsernameError() throws Exception {
mLoginPresenter.validateCredentials("", "testtest");
verify(mLoginViewMock, times(1)).setUsernameError();
verify(mLoginViewMock, never()).setPasswordError();
verify(mLoginViewMock, never()).hideProgress();
}
...
As a workaround you can have this:
public class LoginPresenterImpl ... {
...
#VisibleForTesting
public void setLoginPresenter(LoginPresenter.View loginView) {
this.loginView = loginView;
}
}
In test class:
#Before
public void setUp() {
...
MockitoAnnotations.initMocks(this);
loginPresenter.setLoginPresenter(loginView);
}
But, as a rule of thumb, when you see #VisibleForTesting annotation, that means you have ill architecture. Better to refactor your project.
Heads up to Developers that want to use Android Annotations in their project. Watch out when writing unittests that your code doesn't access the Android APIs. The underlying implementation of Android Annotations is heavily dependent on the Android APIs. So the code that is autogenerated could be dependent on this and make it difficult to write unittests.
Always remember that Android Annotations replaces your class with a final class that has an _ added at the end of it's classname. In this generated class a lot of boilerplate code is autogenerated depending on how the original class is annotated. In my case the problem is that I'm working on an Android-project and want a lot of my methods from my presenter to run on the UI-thread. This is achieved using Android Annotations using the #UIThread annotation. But this means that my method is actually wrapped with another method that calls the super-class:
#Override
public void onUsernameError() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
LoginPresenterImpl_.super.onUsernameError();
return;
}
UiThreadExecutor.runTask("", new Runnable() {
#Override
public void run() {
LoginPresenterImpl_.super.onUsernameError();
}
}
, 0L);
}
My testcase can't get past the line:
...
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
...
And that is of course because we don't have access to the Android APIs in a simple unittest. So in there lies the problem.
Conclusion: You have to be very careful when writing unittests for projects using Android Annotations, that the code that is autogenerated doesn't rely on Android related APIs.
It's the same problem when using androids TextUtil-class.

RxJava+Retrofit 2 unit test weird error

I am creating Android application using MVP pattern.
For that I am using Retrofit 2 and RxJava. App works fine
But in unit testing I am getting weird error.Same test code sometimes passes, sometimes fails.
Error displays with this message
Wanted but not invoked:
albumView.showProgress();
-> at kz.afckairat.kairat.media.AlbumPresenterTest.checkGetPhotoAlbums(AlbumPresenterTest.java:66)
Actually, there were zero interactions with this mock.
Test class
public class AlbumPresenterTest {
enter code here
private MediaService mediaService;
private AlbumView albumView;
private AlbumPresenterImpl photoAlbumPresenter;
#Before
public void setUp() throws Exception {
albumView = mock(AlbumView.class);
mediaService = mock(MediaService.class);
photoAlbumPresenter = new AlbumPresenterImpl(albumView, mediaService, MediaType.PHOTO);
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
#After
public void tearDown() {
RxAndroidPlugins.getInstance().reset();
}
#Test
public void checkGetPhotoAlbums() {
List<Album> albums = getAlbumList();
when(mediaService.getPhotoAlbums()).thenReturn(Observable.just(albums));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showAlbums(albums);
verify(albumView).hideProgress();
}
#Test
public void checkGetPhotoAlbumError() {
String msg = "Error";
when(mediaService.getPhotoAlbums()).thenReturn(Observable.error(new IOException(msg)));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showError(msg);
verify(albumView).hideProgress();
}
private List<Album> getAlbumList() {
List<Album> albums = new ArrayList<>();
Album album = new Album(1, "Test1", "test1.jpg", "01.01.2016", 2);
albums.add(album);
album = new Album(2, "Test2", "test2.jpg", "01.01.2016", 2);
albums.add(album);
return albums;
}
}
Presenter class which is tested
public class AlbumPresenterImpl implements AlbumPresenter {
private AlbumView view;
private MediaType type;
private List<Album> albums;
private MediaService mediaService;
public AlbumPresenterImpl(AlbumView view, MediaService mediaService, MediaType type) {
this.view = view;
this.mediaService = mediaService;
this.type = type;
}
#Override
public void getAlbums() {
Observable<List<Album>> observable;
if (type.equals(MediaType.VIDEO)) {
observable = mediaService.getVideoAlbums();
} else {
observable = mediaService.getPhotoAlbums();
}
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> {
albums = items;
view.showAlbums(albums);
}, throwable -> {
view.showError(throwable.getLocalizedMessage());
});
}
#Override
public void onResume() {
if (albums == null) {
getAlbums();
}
}
#Override
public void onDestroy() {
}
}
Why sometimes test don't pass?
Thanks a lot!
=================================
Update
As #Fred wrote problem was in Schedulers
public class RxSchedulersOverrideRule implements TestRule {
private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() {
#Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
#Override
public Scheduler getNewThreadScheduler() {
return Schedulers.immediate();
}
};
private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
};
// Hack to get around RxJavaPlugins.reset() not being public
// See https://github.com/ReactiveX/RxJava/issues/2297
// Hopefully the method will be public in new releases of RxAndroid and we can remove the hack.
private void callResetViaReflectionIn(RxJavaPlugins rxJavaPlugins)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Method method = rxJavaPlugins.getClass().getDeclaredMethod("reset");
method.setAccessible(true);
method.invoke(rxJavaPlugins);
}
#Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook);
callResetViaReflectionIn(RxJavaPlugins.getInstance());
RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook);
base.evaluate();
RxAndroidPlugins.getInstance().reset();
callResetViaReflectionIn(RxJavaPlugins.getInstance());
}
};
}
}
Code taken from Github a link!
And in Test class
#Rule
public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule();
It seems you override the main thread scheduler with:
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
But from the code, the observables still run on the Schedulers.io() scheduler:
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
// ...
As you may know, the immediate scheduler executes code in the current thread, which I guess since you jump to the io scheduler it's a different one from the one the tests run on.
This will make the test run in one thread and the subscribers/observables in another. This would explain why sometimes the tests pass and sometimes they don't. There's a race condition.
Essential the easiest way is to make sure that at test time you have both observeOn and subscribeOn on Schedulers.immediate() and at run time you have the correct ones, i.e., Schedulers.io() and AndroidSchedulers.mainThread().
You can do this by overriding the schedulers, by passing them as constructors or you could even take a look at this where Dan Lew explains how to use compose to create scheduler transformers. You can then make sure your classes at run time use a proper scheduler transformer and at test time they use some transformer that puts everything on the immediate thread.

Unit testing while using Dagger 2 (Robolectric and Mockito)

I'm trying to write some tests for fragments which have fields annotated with #Inject. For example, a chunk of my app looks like this:
Module:
#Module
public class PdfFactoryModule {
#Provides #Singleton
PdfFactory providePdfFactory() {
return PdfFactory.getPdfFactory();
}
}
Component:
#Singleton
#Component(modules = PdfFactoryModule.class)
public interface CorePdfComponent {
void inject(PagerFragment pagerFragment);
}
Application:
public class CorePdfApplication extends Application {
#NonNull
private CorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerCorePdfComponent.builder().build();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
PagerFragment:
public class PagerFragment extends Fragment {
#Inject PdfFactory pdfFactory;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Dagger 2
((CorePdfApplication) getActivity().getApplication()).getComponent().inject(this);
}
(Note that these are only snippets of my whole code, I'm showing only the essentials for this particular dependency to keep it clear.)
I was trying to do a test like this:
Fake Module:
#Module
public class FakePdfFactoryModule extends PdfFactoryModule {
#Override
PdfFactory providePdfFactory() {
return Mockito.mock(PdfFactory.class);
}
}
Fake Component:
#Singleton
#Component(modules = FakePdfFactoryModule.class)
public interface FakeCorePdfComponent extends CorePdfComponent {
void inject(PagerFragmentTest pagerFragmentTest);
}
Fake Application:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private FakeCorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerFakeCorePdfComponent.builder().build();
}
#NonNull
public FakeCorePdfComponent getComponent() {
return component;
}
}
Test:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
But the DaggerFakeCorePdfComponent doesn't generate. I may have messed up big time because I never tested with dependency injection. What am I doing wrong?
My advice - "Do not use dagger in tests".
Just change your code to next:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private CorePdfComponent component = mock(CorePdfComponent.class);
#Override
public void onCreate() {
super.onCreate();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
And:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
CorePdfComponent component = ((CorePdfApplication)RuntimeEnvironment.application).getComponent();
doAnswer( new Answer() {
Object answer(InvocationOnMock invocation) {
fragment. pdfFactory = mock(PdfFactory.class);
return null;
}
}).when(component).inject(pageFragment);
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
}
You may try:
androidTestApt "com.google.dagger:dagger-compiler:<version>"
I was having similar problem, it worked for me.

IdlingResource Espresso with RxJava

I recently converted my application from using async tasks to rxjava. Now, my espresso tests no longer wait for my data calls to complete due to espresso not having an idling resources for rxjava. I noticed that you can make custom idling resources but I can't figure out how to make it work with rxJava Schedulers, Scheduler.io specifically. Any help/best practice would be greatly appreciated.
Here is how I solved the problem:
IdlingResource implementation:
public class IdlingApiServiceWrapper implements MyRestService, IdlingResource {
private final MyRestService api;
private final AtomicInteger counter;
private final List<ResourceCallback> callbacks;
public IdlingApiServiceWrapper(MyRestService api) {
this.api = api;
this.callbacks = new ArrayList<>();
this.counter = new AtomicInteger(0);
}
public Observable<MyData> loadData(){
counter.incrementAndGet();
return api.loadData().finallyDo(new Action0() {
#Override
public void call() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
counter.decrementAndGet();
notifyIdle();
}
});
}
});
}
#Override public String getName() {
return this.getClass().getName();
}
#Override public boolean isIdleNow() {
return counter.get() == 0;
}
#Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
callbacks.add(resourceCallback);
}
private void notifyIdle() {
if (counter.get() == 0) {
for (ResourceCallback cb : callbacks) {
cb.onTransitionToIdle();
}
}
}
}
and here is my test:
public class MyActivityTest extends ActivityInstrumentationTestCase2<MyActivity> {
#Inject
IdlingApiServiceWrapper idlingApiWrapper;
#Override
public void setUp() throws Exception {
//object graph creation
super.setUp();
getActivity();
Espresso.registerIdlingResources(idlingApiWrapper);
}
public void testClickOpenFirstSavedOffer() throws Exception {
onData(is(instanceOf(DataItem.class)))
.atPosition(0)
.perform(click());
}
}
I used Dagger for dependency injection.
Wrote a little integration piece between RxJava Plugins and Espresso. Hope this helps someone else.
https://gist.github.com/digitalbuddha/d886eae1578bca78b9bf
Edit:
There is a much easier way to accomplish this task. Add the following rule to your tests
public class AsyncTaskSchedulerRule implements TestRule {
final Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxJavaHooks.setOnIOScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnComputationScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnNewThreadScheduler(scheduler -> asyncTaskScheduler);
try {
base.evaluate();
} finally {
RxJavaHooks.reset();
}
}
};
}
}
I am currently using this implementation. Its easier and works very well for me so far: https://github.com/rosshambrick/RxEspresso

Dagger Inject different Dependency to IntentService in prod & test

Is it possible to inject different object through dagger into android.app.IntentService depending if it is a test or production?
this is mainly the code (simplified) which injects the WebRequest Class into the Service.
public class SomeService extends android.app.IntentService {
#Inject
WebReqeust mWebRequest;
public SomeService(String name) {
super(name);
MainApplication.getInstance().inject(this);
}
#Override
protected void onHandleIntent(Intent intent) {
String json = mWebRequest.getHttpString(url);
JSONObject o = new JSONObject(json);
DBHelper.insert(o);
}
}
#Module(injects = { SomeService.class })
public class WebRequestModule {
#Provides
WebRequest provideWebRequest() {
return new WebRequest();
}
}
public class Modules {
public static Object[] list() {
return new Object[] {
new WebRequestModule()
};
}
}
public class MainApplication extends Application {
private ObjectGraph mOjectGraph;
private static MainApplication sInstance;
#Override
public void onCreate() {
sInstance = this;
mOjectGraph = ObjectGraph.create(Modules.list());
}
public void inject(Object dependent) {
mOjectGraph.inject(dependent);
}
public void addToGraph(Object module) {
mOjectGraph.plus(module);
}
}
I would like to write a test which mocks the http response.
I've started with a new Module
#Module(
injects = SomeService.class,
overrides = true
)
final class MockTestModule {
#Provides
WebRequest provideWebRequest() {
WebRequest webRequest = mock(WebRequest.class);
when(webRequest.getJSONObjectResponse(contains("/register/"))).thenReturn(
new JSONObject(FileHelper.loadJSONFromAssets(this.getClass(),
"mock_register.json")));
when(webRequest.getJSONObjectResponse(contains("/register_validate/"))).thenReturn(
new JSONObject(FileHelper.loadJSONFromAssets(this.getClass(),
"mock_register_validate.json")));
return webRequest;
}
}
And in the test i tried the following
public class RegisterTest extends AndroidTestCase {
protected void setUp() throws Exception {
MainApplication.getInstance().addToGraph(new MockTestModule());
super.setUp();
}
public void test_theActuallTest() {
Registration.registerUser("email#email.com"); // this will start the service
wait_hack(); // This makes the test wait for the reposen form the intentservice, works fine
DBHelper.isUserRegisterd("email#email.com"));
}
}
The test is executed successfull (remember, the code is simplyfied and might not compile, just should represent the idea).
However, it still uses the "real" WebRequest Impl., not the Mocked one. I see it in the logs, the proxy and of ourse on the server ...
I did this with RoboGuice in a very similar way and it was working.
But somehow i am not able to get this done with dagger.
(I'm currently evaluating DI Frameworks and this is a "must have")
The plus method actual returns the new graph. It doesn't override the original graph. That being said to accomplish what you want you can simply do this.
public class MainApplication extends Application {
...
// Mostly used for testing
public void addToGraph(Object module) {
mObjectGraph = mOjectGraph.plus(module);
}
}
This takes the original graph and pluses it with your new module and then simply assigns the new graph to your mObjectGraph reference.

Categories

Resources