Using Dagger and Robolectric with test application - android

I am using MVP pattern with a Fragment(GalleryFragment), where Application class(MainApplication) sources MainActivityRepository and GalleryFragmentPresenter(grouped as DIModules) which are provided to Fragment through field injection.
To test GalleryFragment in isolation, my idea was to use Robolectric configuration(#Config) to replace MainApplication entirely with a custom TestApplication sourcing mockDIModules.
GalleryFragmentTest runs until startFragment(galleryFragment) but I get a NullPointerException at MainApplication.getComponent().inject(this); inside GalleryFragment.
I suspect this is because this line specifically uses MainApplication while everything else is dealt with TestApplication set by Robolectric #Config, but I'm not sure and I am looking for advice on how to successfully run tests using this custom TestApplication.
While searching for possible solutions, I found out about using AndroidInjector from Dagger support library, which will get rid of MainApplication.getComponent().inject(this); entirely but would this work?
https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3
GalleryFragment.java
public class GalleryFragment extends Fragment {
#Inject
public MainActivityRepository mRepository;
#Inject
public GalleryFragmentPresenter mGalleryFragmentPresenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainApplication.getComponent().inject(this); //NullPointerException here
mGalleryFragmentPresenter.initialize(mRepository, value);
}
}
DIModules.java
#Module
public class DIModules {
#Provides
public GalleryFragmentPresenter provideGalleryFragmentPresenter(){
return new GalleryFragmentPresenter();
}
#Provides
#Singleton
public MainActivityRepository provideMainActivityRepository(){
return new MainActivityRepository();
}
}
AppComponent.java
#Singleton
#Component(modules = DIModules.class)
public interface AppComponent {
void inject(GalleryFragment galleryFragment);
}
MainApplication.java
public class MainApplication extends Application {
public static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
Realm.init(this);
component = buildComponent();
}
public static AppComponent getComponent() {
return component;
}
protected AppComponent buildComponent(){
return DaggerAppComponent
.builder()
.dIModules(new DIModules())
.build();
}
}
TestApplication.java
public class TestApplication extends Application {
public static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = buildComponent();
}
public static AppComponent getComponent() {
return component;
}
protected AppComponent buildComponent(){
return DaggerAppComponent.builder()
.dIModules(new mockDIModules())
.build();
}
}
GalleryFragmentTest.java
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class,
application = TestApplication.class)
public class GalleryFragmentTest {
#Test
public void allItemTabTest() throws Exception {
GalleryFragment galleryFragment = GalleryFragment.newInstance(value);
startFragment(galleryFragment);
assertNotNull(galleryFragment);
}
}
I am using dagger, dagger-android-support, dagger-compiler version 2.14.1 and robolectric:3.6.1

Of course, it is null. Your fragment still tries to work with production application while you're doing things in the test application.
Change your injection code to next:
((MainApplication) getContext().getApplicationContext()).getComponent().inject(this);
And also make the method in your Application getComponent() as not static, so test app overrides it.
Another option is to change your TestApplication to next:
public class TestApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
buildComponent();
}
private void buildComponent(){
Application.component = DaggerAppComponent.builder()
.dIModules(new mockDIModules())
.build();
}
}

Related

Dagger2 Injection Unit Tests is null

Hi i have used dagger for dependency injections of Network Module, ApplicationModule, DatabaseModule, Presenters and interactor in my app.
I want to use these same classes and Module during unit testing.
As unit testing reference, i have created AndroidTestAppComponent using following code:
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
AndroidTestAppModule.class,
NetworkModule.class
})
public interface AndroidTestAppComponent extends AndroidInjector<AndroidTestApplication> {
#Component.Builder
abstract class AndroidTestAppComponentBuilder extends Builder<AndroidTestApplication> {
}
}
Giving all module is out of scope for this question, consider AndroidTestAppModule.java below:
public class AndroidTestAppModule {
#Provides
#Singleton
Context provideContext(AndroidTestApplication application) {
return application.getApplicationContext();
}
#Singleton
#Provides
KeyguardManager provideKeyguardManager(Context context) {
return (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
}
#Singleton
#Provides
FingerprintManagerCompat providerFingerPrintManager(Context context) {
return FingerprintManagerCompat.from(context);
}
}
I am able to generate DaggerAndroidTestAppComponent.
My Application class is as below:
public class AndroidTestApplication extends DaggerApplication implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
AndroidInjector<AndroidTestApplication> androidInjector;
#Override
public void onCreate() {
super.onCreate();
androidInjector.inject(this);
}
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
androidInjector = DaggerAndroidTestAppComponent.builder().create(this);
return androidInjector;
}
#Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
Some other AppPref.java class
#Singleton
public class AppPref {
private SharedPreferences preferences;
#Inject
AppPref(Context context) {
preferences = context.getSharedPreferences("somefile", Activity.MODE_PRIVATE);
}
}
As read from documentation: AndroidInjection#inject(T t) t here takes core android module, so when i call this in my Activity AndroidInjection.inject(activity_reference_usually__this__) it works(Normal scenario, real build and no testing app)
Without changing much code how can i use these Classes in AndroidInstrumentationTest, because i will only change test implementation in Test**DaggerModules inside test package.
Sample code for instrumentation is given below:
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
AndroidTestApplication application;
#Inject
AppPref appPref;
#Before
public void setUp() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Context appContext = InstrumentationRegistry.getTargetContext();
application = (AndroidTestApplication) Instrumentation.newApplication(AndroidTestApplication.class, appContext);
DaggerAndroidTestAppComponent.builder().create(application).inject(application);
}
#Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.a.b", appContext.getPackageName());
}
#Test
public void testPreNotNUll() {
Assert.assertNotNull(appPref);
}
}
Ideally, apppref is alwyas null, becuase in setUp method i have injected AndroidTestApplication class and not in ExampleInstrumentedTest how can i edit my dagger2 code so that #Inject works fine and i get valid appPref object.
Thank you.
You are actually not injecting anything into your Test class.
DaggerAndroidTestAppComponent.builder().create(application).inject(application);
You are injecting into AndroidTestApplication instead of your Test.
Try to add
void inject(ExampleInstrumentedTest test);
Into your Component interface.
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
androidInjector = DaggerAndroidTestAppComponent.builder().create(this);
return androidInjector;
}
Here you are creating your Dagger Component, no need to do it again in the Test.
Make androidInjector to be AndroidTestAppComponent instead of AndroidInjector in your AndroidTestApplicaiton, make a getter for that Component in your AndroidTestApplication and then in your Test setUp method use application.getComponent().inject(this);
That way you are injecting dependencies into desired class which is your Test.
I had to modify #Component interface to skip extending builder from AndroidInjector.Builder and provide my own approach.
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
AndroidTestAppModule.class,
NetworkModule.class
})
public interface AndroidTestAppComponent extends AndroidInjector<AndroidTestApplication> {
void inject(ExampleInstrumentedTest test);
#Component.Builder
abstract class AndroidTestAppComponentBuilder {
#BindsInstance
public abstract AndroidTestAppComponentBuilder application(AndroidTestApplication application);
public abstract AndroidTestAppComponent build();
}
}
Such that i had to manually pass application and build the component, then as suggested by tuby, i had to add new method void inject(ExampleInstrumentedTest test) to #Component interface.
My test class now looks like this and i am able to run test and get coverage[jacoco tool]:
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
#Inject
AppPref appPref;
#Before
public void setUp() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Context appContext = InstrumentationRegistry.getTargetContext();
AndroidTestApplication application = (AndroidTestApplication) Instrumentation
.newApplication(AndroidTestApplication.class, appContext);
DaggerAndroidTestAppComponent.builder().application(application)
.build()
.inject(this);
}
#Test
public void test1AppPrefNotNUll() {
Assert.assertNotNull(appPref);
}
private final String KEY = "key";
private final String valid = "test_app";
private final String invalid = "non_app";
#Test
public void test2AppPrefWrite() {
appPref.writePreference(KEY, valid);
Assert.assertNotNull(appPref.readPreference(KEY));
}
#Test
public void test3AppPrefRead() {
Assert.assertEquals(valid, appPref.readPreference(KEY));
}
#Test
public void test4AppPrefInvalid() {
Assert.assertNotNull(invalid, appPref.readPreference(KEY));
}
#Test
public void test5AppPrefClear() {
appPref.clearPreferences();
Assert.assertEquals(0, appPref.size());
}
}

Dagger 2 returns null after injection

I am trying to make an injection using Dagger 2, but it always returns null. I think I am doing all right, but anyway it does not work.
Here is the application class:
public class ApplicationSA extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return appComponent;
}
}
The component interface:
#Component(modules = {
SnoreDetectorClass.class,
AudioRecorderClass.class
})
public interface AppComponent {
void injectsMainFunctionalityActivity(Activity activity);
}
An the main class where I am trying to get the object:
public class MainFunctionalityActivity extends AppCompatActivity {
#Inject
AudioRecorderClass audioRecorderClass;
#Inject
SnoreDetectorClass snoreDetectorClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
ApplicationSA.getComponent().injectsMainFunctionalityActivity(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("TESTING","audioRecorderClass= "+audioRecorderClass); // always null
Log.d("TESTING","snoreDetectorClass= "+snoreDetectorClass); // always null
...
}
And here are the module classes:
#Module
public class AudioRecorderClass {
public interface AudioRecorderInterface {
void AudioRecorder_hasUpdate(double amplitude_in_dB);
}
public AudioRecorderInterface delegate = null;
#Provides
AudioRecorderClass provideAudioRecorderClass(Activity activity) {
delegate = (AudioRecorderInterface)activity;
return new AudioRecorderClass();
}
...
#Module
public class SnoreDetectorClass {
#Provides
SnoreDetectorClass provideSnoreDetectorClass() {
return new SnoreDetectorClass();
}
...
What am I doing wrong ? Why the objects are always null ?
Ah, I see what is going on here. You cannot inject into a subclass. So in your AppComponent you cannot have
void injectsMainFunctionalityActivity(Activity activity);
you must inject with
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);
As a side note I would suggest not combining your injector and your model class. Better to have separation of concerns. Keep them separate
You have to specifically tell dagger which activity will be injected here, not use the super class Activity but rather your own implementation of the Activity class :
void injectsMainFunctionalityActivity(Activity activity);
change to:
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);

Using two Dagger components in application class

I'm trying to understand Dagger2. I've followed a few examples and using one component/module makes sense but adding a second confuses me. Am I not able to use more than one component in my application class?
Both Dagger components are highlighted red and say "Cannot resolve symbol..."
public class MyApplication extends Application {
StorageComponent component;
ImageDownloaderComponent imageDownloaderComponent;
#Override
public void onCreate() {
super.onCreate();
component = DaggerStorageComponent
.builder()
.storageModule(new StorageModule(this))
.build();
imageDownloaderComponent = DaggerImageDownloaderComponent
.builder()
.imageDownloaderModule(new ImageDownloaderModule(this))
.build();
}
public StorageComponent getComponent() {
return component;
}
public ImageDownloaderComponent getImageDownloaderComponent() {
return this.imageDownloaderComponent;
}
}
I ended up putting both pieces (SharedPrefs and Picasso) in one module and returned the component in my application class. Hope the wording makes sense.
#Module
public class StorageModule {
private final MyApplication application;
public StorageModule(MyApplication application) {
this.application = application;
}
#Singleton
#Provides
SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(application);
}
#Singleton
#Provides
ImageDownloader provideImageDownloader() {
return new ImageDownloader(application);
}
}
MyApplication class:
public class MyApplication extends Application {
StorageComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerStorageComponent
.builder()
.storageModule(new StorageModule(this))
.build();
}
public StorageComponent getComponent() {
return component;
}
}

Android Dagger2 Dependency Injection

I'm new to Dagger2 and I'm trying to use dependency injection in my application.
I'm using shared preferences and thought it will be more helpful to use dependency injection instead of getting an instance of shared prefrences each time I need to use it.
It works fine when I'm using it on activities and fragments but when I'm trying to use it on service or intentservice it doesn't work.
Here is my code:
AppModule:
#Module
public class AppModule
{
public final ApplicationClass application;
public AppModule(ApplicationClass application)
{
this.application = application;
}
#Provides #Singleton
Context providesApplicationContext()
{
return this.application;
}
#Provides #Singleton
SharedPreferences providesSharedPreferences()
{
return application.getSharedPreferences(Constants.FILE_NAME,Context.MODE_PRIVATE);
}
}
AppComponent
#Singleton #Component(modules = {AppModule.class})
public interface AppComponent
{
void inject (ApplicationClass applicationClass);
void inject (IntentService intentService);
void inject (Service service);
}
ApplicationClass
public class ApplicationClass extends Application
{
AppComponent appComponent;
#Override
public void onCreate()
{
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(new
Thread.UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread t, Throwable e) {
onUncaughtException(t, e);
}
});
appComponent = DaggerAppComponent
.builder()
.appModule(new AppModule(this))
.build();
appComponent.inject(this);
}
public AppComponent getAppComponent()
{
return this.appComponent;
}
private void onUncaughtException(Thread t, Throwable e)
{
e.printStackTrace();
Intent crash= new Intent(getApplicationContext(),Crash.class);
about.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(crash);
}
}
So I tried to inject the shared preferences in IntentService and I used these lines of code
inside the onCreate method of the my service (intentservice)
#Inject
SharedPreferences preferences;
#Override
public void onCreate()
{
super.onCreate();
((ApplicationClass)getApplication()).getAppComponent().inject(this);
}
But the problem is when I use this preferences variable in the onHandleIntent method, the application is crashing because the preferences is null..
So why it doesn't inject?
For anyone who encountered this problem.
As Vadim Korzun and EpicPandaForce mentioned in their comments above,
I should specify the specific class in the inject method.
So in my case my IntentService class was named GeofenceService
and inside the AppComponent interface I should write
void inject (GeofenceService service);
Same thing for Service
UPDATE:
So for all the people who have multiple Services that inherit from IntentService and want to save themselves from writing inject method for each Specific service.
I would suggest to do the steps below:
Create BasicIntentService that extends IntentService
In your AppComponent interface add the inject method which take as parameter you BasicIntentService.
In your BasicIntentSerive, you will have a protected SharedPrefrences variable annotated with the Inject annotation.
Still, in your BasicIntentService, inside the onCreate method you will call this line of code
((ApplicationClass)getApplication()).getAppComponent().inject(this);
Now each IntentService that you will create will extends the BasicIntentService and you will be able to use the SharedPreferences variable.
AppComponent:
#Singleton #Component(modules = {AppModule.class})
public interface AppComponent
{
void inject (YourApplicationClass applicationClass);
void inject (BasicIntentService intentService);
}
BasicIntentService
public class BasicIntentService extends IntentService
{
#Inject
protected SharedPreferences sharedPreferences;
#Override
public void onCreate()
{
super.onCreate()
((YourApplicationClass)getApplication()).getAppComponenet().inject(this);
}
}
SomeIntentService
public class SomeIntentService extends BasicIntentService
{
#Override
public void onCreate()
{
super.onCreate();
}
-----------
#Override
protected void onHandleIntent(#Nullable Intent intent)
{
// some code
if (sharedPreferences.contains(someKey))
{
// some code
}
else
{
// some code
}
}
}

Dagger automatically persisting data

I have a class that's being injected using DI via dagger. However when the activity is destroyed and recreated, the model class seems to contain the data automatically without me insert/repopulating the data.
public class MyApplication extends Application {
private ExpressionFactoryComponent mExpressionFactoryComponent;
#Override
public void onCreate() {
super.onCreate();
// Building dagger DI component
mExpressionFactoryComponent = DaggerExpressionFactoryComponent.builder().
expressionFactoryModule(new ExpressionFactoryModule(new ExpressionFactory(this))).build();
}
}
Module:
#Module
public class ExpressionFactoryModule {
private ExpressionFactory mExpressionFactory;
public ExpressionFactoryModule(ExpressionFactory expressionFactory) {
this.mExpressionFactory = expressionFactory;
}
#Provides
ExpressionFactory provideStringExpressionFactory() {
return mExpressionFactory;
}
}
The one reason for this is that ExpressionFactory is instantiated in MyApplication class and then passed into the constructor of ExpressionFactoryModule while creating ExpressionFactoryComponent. You should pass an Application instance in your module constructor and then create an instance of your class withing a metod with #Provide annotation passing that Application instance in your class's constructor.
This how things should be done with dagger. But to solve your problem you need to create another component class and build the component in an activity if you need to have an instance of your class living withing an activity only.
Here is the solution (ExpressionFactoryComponent is renamed to AppComponent here):
public class MyApplication extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
// Building dagger DI component
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this)).build();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
#Component(modules = {AppModule.class})
public interface AppComponent {
Application getApplication();
void inject(App application);
}
#Module
public class AppModule {
private Application application;
public AppModule(Application application) {
this.application = application;
}
#Provides
Application provideApplication() {
return application;
}
}
#Component(dependencies = AppComponent.class, modules = {
ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity activity);
}
#Module
public class ActivityModule {
private Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
#Provides
Activity provideActivity() {
return activity;
}
#Provides
ExpressionFactory provideStringExpressionFactory(Application application) {
return new ExpressionFactory(application);
}
}
public class MainActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityComponent activityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(activity))
.appComponent(App.getAppComponent())
.build();
activityComponent.inject(this);
}
}

Categories

Resources