RoboGuice unit test injecting app module instead of test module - android

I am trying to write a JUnit test for an Android Service using RoboGuice 2.0. I have a test module that binds injected dependencies to Mockito mock objects. However, when I run the test, the real implementations from my app module get injected instead. Here is some of the relevant code:
MainApplication.java:
public class MainApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(this), new MainModule());
startService(new Intent(this, NotificationService.class));
}
}
MainModule.java:
public class MainModule extends AbstractModule {
#Override
protected void configure() {
bind(IFooManager.class).to(FooManagerImpl.class).in(Scopes.SINGLETON);
}
}
NotificationService.java:
public class NotificationService extends RoboService {
#Inject
private NotificationManager notificationManager;
#Inject
private SharedPreferences prefs;
#Inject
private IFooManager fooManager;
private IFooListener listener = new FooListener();
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
fooManager.addListener(listener);
}
#Override
public void onDestroy() {
super.onDestroy();
fooManager.removeListener(listener);
}
private class FooListener implements IFooListener {
// Do stuff that fires Notifications
}
}
NotificationServiceTest.java:
public class NotificationServiceTest extends ServiceTestCase<NotificationService> {
#Mock
private MockFooManager fooManager;
#Mock
private MockSharedPreferences prefs;
public NotificationServiceTest() {
super(NotificationService.class);
}
public void testStart() {
startService(null);
verify(fooManager).addListener(isA(IFooListener.class));
}
public void testStop() {
shutdownService();
verify(fooManager).removeListener(isA(IFooListener.class));
}
#Override
protected void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
Application app = new MockApplication();
setApplication(app);
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, new TestModule());
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
RoboGuice.util.reset();
}
private class TestModule extends AbstractModule {
#Override
protected void configure() {
bind(Context.class).toInstance(getContext());
bind(IFooManager.class).toInstance(fooManager);
bind(SharedPreferences.class).toInstance(prefs);
}
}
}
MockFooManager and MockSharedPreferences are empty abstract implementations of IFooManager and SharedPreferences, needed because RoboGuice can't inject mocks of interfaces. I am using Mockito with Dexmaker to support bytecode generation for mocked classes. Also, I am not using Robolectric, I am running these tests on a device or in the emulator.
When I run this test, I get the error Wanted but not invoked: fooManager.addListener(isA(com.example.IFooListener)). After stepping through this with the debugger, I found that RoboGuice is injecting the dependencies from MainModule instead of TestModule, so the test is using FooManagerImpl instead of MockFooManager. I don't understand how RoboGuice even knows about MainModule in the test code.
Here are some other things I tried to fix this, but none had any effect:
Specify app modules in roboguice.xml instead of calling RoboGuice.setBaseApplicationInjector in MainApplication.onCreate
Use Modules.override when calling RoboGuice.setBaseApplicationInjector instead of just passing the module list directly.
How do I get RoboGuice to use TestModule and ignore MainModule in my unit test?

It seems like you are missing a call to do the injection in your NotificationServiceTest. This is done as follows:
RoboGuice.getInjector(app).injectMembers(this);
You will need to add this at some point after you set the base injector and before the tests are run.

Use
RoboGuice.overrideApplicationInjector(app,RoboGuice.newDefaultRoboModule(app), new TestModule())
instead of
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, new TestModule());

Related

JUnit Test return lateinit property <nameOfInstance>has not been initialized on presenter

I wanna t create a simple JUnit testing an the test return an error like
kotlin.UninitializedPropertyAccessException: lateinit property
mInstance has not been initialized
my tests I will write in Java. Here is the test class. My presenter and all classes is written in Kotlin with Dagger2 and RxJava.
public class PhoneNumberPresenterTest {
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Mock
private PhoneNumberContract.View view;
#Inject
private PhoneNumberPresenter phoneNumberPresenter;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
phoneNumberPresenter = new PhoneNumberPresenter(this.view);
}
#Test
public void fetchValidDataShouldLoadIntoView() {
SendCodeRequest request = new SendCodeRequest(anyString());
GenericResponse<User> response = new GenericResponse<>();
when(phoneNumberPresenter.mUserService.sendCode(request))
.thenReturn(Observable.just(response));
}
}
In My Presenter I do that
class PhoneNumberPresenter() : PhoneNumberContract.Handler {
#Inject
lateinit var mUserService: UserService
init {
Application.mInstance.mComponent.inject(this)
}
//.....
mUserService.sendPhoneNumber().....
}
In AppComponent class that
#Singleton
#Component(modules = arrayOf(HttpModule::class, AppModule::class))
interface AppComponent {
fun inject(presenter: PhoneNumberPresenter)
}
And the error of test is that "lateinit property mUserService has not been initialized"
I don't use Dagger for unit testing if I can avoid it because I want to control what's a mock, what's a spy, etc.
You should not use any "Application.mInstance" with Dagger. It seems like you use Dagger before version 2.10 - you should upgrade to the latest and greatest! There is a whole section about Android on https://google.github.io/dagger/android
With Dagger 2.10 and upwards you can inherit from DaggerApplication and then use AndroidInjection.inject() for Activities and #Inject on the constructor for your own classes.
public class MyApplication extends DaggerApplication {
/**
* This is the same as extending from MultiDexApplication.
* #see android.support.multidex.MultiDexApplication
*/
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
#Override
public void onCreate() {
super.onCreate();
// ...
}
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}
Your activities should look like this:
public class YourActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
For testing of activities, maybe you should use a TestApplication instead of your real Application class? See https://android.jlelse.eu/testing-your-app-with-dagger-2-c91cdc0860fb for some hints about that.

Mock injected ViewModel

I need to run Android instrumented test on StarterActivity. Here is how it goes
public class StarterActivity extends BaseActivity<ActivityStarterBinding> {
#Inject
protected StarterViewModel starterViewModel;
#Override
public int getContentView() {
return R.layout.activity_starter;
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getApplicationComponent().inject(this);
}
//...
}
And I call for starterViewModel method in onStart.
StarterViewModel is injected via a constructor:
public class StarterViewModel {
private final AuthDataModel authDataModel;
#Inject
public StarterViewModel(AuthDataModel authDataModel) {
this.authDataModel = authDataModel;
}
#NonNull
public Single<Boolean> isUserLoggedIn() {
return authDataModel.isUserLoggedIn();
}
}
I found this really nice approach Android testing using Dagger 2, Mockito and a custom JUnit rule. But it needs me to add #Provide method. And application component is going to become "God component" with dependencies on a bunch of Modules (or one "God module").
How can I mock in Espresso test without adding #Provide method and overriding it in tests?

Mocking Injected Constructors in Dagger 2

I have an activity with a dependency:
public class MyActivity extends AppCompatActivity {
#Inject Dependency;
#Override
protected void onCreate(Bundle savedInstanceState) {
// inject
}
}
public class Dependency {
#Inject
public Dependency() {
//..
}
}
Since Dependency has an injected constructor, Dagger2 doesn't require a module to know how to instantiate it, which is super convenient.
My question is: For testing purposes, do I have to have an explicit module that provides Dependency in order to be able to mock it and provide a mock version of Dependency? or is there a way to mock Dependency without it?
I found a way without creating an explicit Module. Here's how I did it using Robolectric and Mockito:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
public class MyActivityTest {
#Mock AppComponent mAppComponent;
#Mock private Dependency mDependency;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
// ***
// use the mock AppComponent to perform injections
// ***
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
((MyActivity) invocation.getArguments()[0]).mDependecy = mDependecy;
return null;
}
}).when(mAppComponent).inject(any(MyActivity.class));
}
}

Cant inject classes using Dagger on Android

I am beggining with Dagger, I am using 1.2 version of it, and I have the following scenario:
Module:
#Module(injects = {
AuthenticationService.class
})
public class ServiceModule {
#Provides
AuthenticationService provideAuthenticationService() {
return ServiceFactory.buildService(AuthenticationService.class);
}
}
On my Application class I create the ObjectGraph:
public class FoxyRastreabilidadeApplication extends Application {
private static FoxyRastreabilidadeApplication singleton;
#Override
public void onCreate() {
super.onCreate();
createObjectGraph();
singleton = this;
}
private void createObjectGraph() {
ObjectGraph.create(ServiceModule.class);
}
}
and finally, at my LoginActivity, I try to inject my AuthenticationService:
public class LoginActivity extends Activity implements LoaderCallbacks<Cursor> {
private UserLoginTask mAuthTask = null;
#Inject
AuthenticationService authenticationService;
}
At this point, when I try to access my AuthenticationService instance it is always null, meaning it wasnt injected at all, I debugged my provider method to be sure of it, so, the question is, am I missing something? If so, what is it?
You need to hold on to the ObjectGraph and ask it to inject your objects directly.
Add an instance variable to your custom Application subclass to hold on to the created ObjectGraph:
this.objectGraph = ObjectGraph.create(ServiceModule.class);
Add a convenience method to your Application to do injection:
public void inject(Object object) {
this.objectGraph.inject(object);
}
Call that method wherever you need injection to happen, for example in your Activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication)getApplication()).inject(this);
...
}

Dagger 1.x #Inject throws an IllegalArgumentException

I want to replace our component registry (with dexfile class loader magic) with an dependency injection framework for Android.
The first try is dagger.
When trying I get the following error:
11-06 13:05:41.040 16269-16269/com.daggertoolkitexample E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{daggertoolkitexample/com.dagger.MyActivity}: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2295)
...
Caused by: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at dagger.ObjectGraph$DaggerObjectGraph.getInjectableTypeBinding(ObjectGraph.java:302)
at dagger.ObjectGraph$DaggerObjectGraph.inject(ObjectGraph.java:279)
at com.dagger.MyApplication.inject(MyApplication.java:39)
at com.dagger.MyBaseActivity.onCreate(MyBaseActivity.java:18)
at com.dagger.MyActivity.onCreate(MyActivity.java:22)
at android.app.Activity.performCreate(Activity.java:5372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1104)
...
I can fix it if i inject my activity in the #Module. Its work without exception.
#Module(
library = true,
injects = MyActivity.class)
public class AuthManagementModul {...}`
But this is not that i want.
I don´t can and want to know all users of my component.
Has everyone an idea what's wrong?
Here is my example code:
public class MyBaseActivity extends ActionBarActivity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).inject(this);
}
}
...
public class MyActivity extends MyBaseActivity {
#Inject AuthManagement authManagement;
...
}
...
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(new AuthManagementModul(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
...
#Module(
library = true
)
public class AuthManagementModul {
private final Application application;
public AuthManagementModul(Application application) {
this.application = application;
}
#Provides
#Singleton
AuthManagement provideAuthManagement() {
return new AuthManagementImpl(application);
}
}
In this case, you don't want to add injects= to your AuthManagementModul, but rather to an activity-specific module which includes it.
Dagger 1.x uses injects= as a signal for what graph-roots to analyze, so they must be present -but they need not be present on leaf-node library modules - just on a module the activity uses. Consider breaking up your modules on more partitioned lines like so:
#Module(
injects = {
... all your activities
},
includes = {
AuthManagementModul.class,
ApplicationModule.class
}
)
class EntryPointsModule {}
#Module(library = true, complete = false)
class AuthManagementModul {
#Provides
#Singleton
AuthManagement provideAuthManagement(Application application) {
return new AuthManagementImpl(application);
}
}
#Module(library = true)
class ApplicationModule {
private final Application application;
public ApplicationModule(Application application) {
this.application = application;
}
#Provides
#Singleton
Application application() {
return application;
}
}
Then create your graph like so:
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
// AuthManagementModul is automatically included because it has a default
// constructor and is included by EntryPointsModule
graph = ObjectGraph.create(new EntryPointsModule(), new ApplicationModule(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
There are other ways to structure this - you could just have ApplicationModule include the AuthModule and declare injects, so you only have two modules, etc. I suggested this way because ApplicationModule is then a separate concern whose only role is to hoist the Application instance into the graph, AuthManagementModul is exclusively there to support the auth function, and EntryPointsModule is there to be the front of the whole graph.
If you migrate to Dagger2, this structure is also convenient in that EntryPointsModule naturally converts to a #Component.

Categories

Resources