Dagger used in Robolectric tests - android

I want to use Dagger injections in my robolectric tests but I have trouble with set it up. Where is error in my code sample. How can I make this work?
My main module
#Module(
includes = DatabaseModule.class,
injects = {
MainActivity.class,
}
)
public class MainModule {
private final MyApplication application;
public MainModule(MyApplication application) {
this.application = application;
}
My test module
#Module(
overrides = true,
includes = MainModule.class,
injects = {
TestMyApplication.class,
MyApplication.class
}
)
public class TestModule {
}
My production main class
public class MyApplication extends Application {
#Inject
public MyApplication() {
super();
}
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
inject(this);
}
...
}
My robolectric test application class
public class TestMyApplication extends MyApplication {
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
return modules;
}
public void injectMocks(Object object) {
((TestMyApplication) Robolectric.application).inject(object);
}
Error:
java.lang.RuntimeException: java.lang.IllegalArgumentException: No inject registered for members/info.korzeniowski.MyApplication.TestMyApplication. You must explicitly add it to the 'injects' option in one of your modules.
When I change in My robolectric application class method to this:
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
modules.add(new TestModule());
return modules;
}
Result:
java.lang.RuntimeException: java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.TestModule could not be loaded. Please ensure that code generation was run for this module.
Update
gradle.build:
compile 'com.jakewharton:butterknife:5.1.2'
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
compile "com.squareup:javawriter:2.2.1"

You should provide the TestModule and you are doing it right (TestModule must be in the list of modules returned from getModules). You can find solution for the second error here: Dagger example built through eclipse fails with 'Please ensure that code generation was run for this module.'

Related

Getting null pointer object on hilt inject object in activity when run test cases

Please check below my Module class in which I have defined my object which need to be inject using Hilt
NVModule.kt
#Module
#InstallIn(SingletonComponent::class)
class NVModule {
#Provides
#Named("ProfileHelper")
fun abprovideProfileHelper(): ProfileHelper {
return ProfileHelper(AppController.getInstance())
}
}
And now please check my Interface by which i have used the EntryPoint to access my dependency injection outside the Activity/Fragment like Helper class.
#EntryPoint
#InstallIn(SingletonComponent.class)
public interface CommonHiltInterface {
#Named("ProfileHelper")
public ProfileHelper provideProfileHelper();
}
}
Now please check the my Activity class on which i have used the dependency injection like below and it is working fine here. Means getting dependency injection properly
public class HomeActivity extends BaseActivity{
private ActivityHomescreenBinding
activityHomescreenBinding;
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
activityHomescreenBinding =
DataBindingUtil.inflate(getLayoutInflater(),
R.layout.activity_homescreen, null, false);
setContentView(activityHomescreenBinding.getRoot());
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();
}
}
But in case of the Test cases , dependency injection getting NullPointerException . I am using the Robolectric for the test cases. Please check my below lines of code for the RobolectricTest case.
#HiltAndroidTest
#RunWith(RobolectricTestRunner.class)
#Config(application = HiltTestApplication.class,
sdk = Build.VERSION_CODES.N, manifest = Config.NONE)
public class HomeActivityTest {
#Rule
public HiltAndroidRule hiltRule = new
HiltAndroidRule(this);
#Before
public void setUp() throws Exception {
shadowOf(Looper.getMainLooper()).idle();
hiltRule.inject();
activity =
Robolectric.buildActivity(HomeActivity.class).
create().resume().get();
}
}
Note :- 1). I have also use #HiltAndroidApp() for application class.and using 2.36 version for hilt dependency
2). My dependency injection working fine for the Java classes like Activity/Fagment and Helper classes , But not working in test cases.
Please check my dependency for Hilt are as follow
testImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptTest 'com.google.dagger:hilt-android-compiler:2.36'
testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.36'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.36'
Application runs successfully but in case of Test case I am getting the null pointer exception below lines of code in Activity (HomeActivity).
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();

Dagger 2 can not resolve symbol 'DaggerAppComponent'

I'm trying to create a simple app with retrofit 2, dagger 2 and MVP, but I struggle with dependencies, actualy, this is the error I get after i try to rebuild the project Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.StackOverflowError
and also in App class where I provide AppComponent: can not resolve symbol 'DaggerAppComponent'
I'll try to show you what my project looks like so someone can see the problem, First one is my AppModule which includes PresentationModule.class
#Module(includes = PresentationModule.class)
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
#Provides
#Singleton
public App provideApp() {
return app;
}
}
Presentation Module looks like this:
#Module(includes = InteractorModule.class)
public class PresentationModule {
#Provides
JokePresenter providePresenter(JokeInteractor jokeInteractor) {
return new JokePresenterImpl(jokeInteractor);
}
}
And InteractorModule:
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule {
#Provides
JokeInteractor provideJokeInteractor(RetrofitService service, JokePresenter presenter) {
return new JokeInteractorImpl(service, presenter);
}
}
This is JokePresenter that has a reference to view and interactor:
public class JokePresenterImpl implements JokePresenter {
private JokeView mJokeView;
private List<String> mData = new ArrayList<>();
private String mJoke;
private JokeInteractor jokeInteractor;
public JokePresenterImpl(JokeInteractor jokeInteractor) {
this.jokeInteractor = jokeInteractor;
}
#Override
public void setView(JokeView view) {
this.mJokeView = view;
}
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke();
}
}
And JokeInteractor that has a RetrofitService and JokePresenter references:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
private JokePresenter presenter;
public JokeInteractorImpl(RetrofitService service, JokePresenter presenter) {
this.retrofitService = service;
this.presenter = presenter;
}
#Override
public void getRandomJoke() {
retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
presenter.onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
presenter.onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
presenter.onNextRandomJoke(randomJokeResponse);
}
});
}
Gradle dependencies:
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
provided 'javax.annotation:jsr250-api:1.0'
//retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//rx java
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
//dagger2
compile 'com.google.dagger:dagger:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Can someone see the problem here?
Look at the implementation of:
#Module(includes = InteractorModule.class)
public class PresentationModule
and
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule
You have a cyclic dependency. You need to rethink your design. I propose to decouple Interactor from Presenter.
A simple solution:
Change getRandomJoke() implementation to return Observable and subscribe to it inside Presenter. And remove presenter reference from Interactor.
Interactor:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
public JokeInteractorImpl(RetrofitService service) {
this.retrofitService = service;
}
#Override
public Observable getRandomJoke() {
return retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Presenter:
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke()
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
onNextRandomJoke(randomJokeResponse);
}
});
}
Make sure that dependencies added in your gradle
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
Make sure adding AndroidSupportInjectionModule::class inside your modules.
Like this:
#Component(
modules = [
AndroidSupportInjectionModule::class,
....//Other module classes
]
)
This is what worked for me (Solution) :-
If you have any other build issues related to Dagger apart from DaggerAppComponent import issue, then make sure to fix them first, if not we will just end up wasting time.
If you fix the other Dagger issues, & then build your project.
The DaggerAppComponent will get generated, & then all you have to do is import it.
Brief info regarding the issue that I faced :-
The mistake that I did was that, I had another Dagger build issue, but I ignored it & spent my time trying to fix 'DaggerAppComponent' import problem.
Wont be relevant to you but in my case, the DI module class annotated with #Module had a single abstract method, & hence the module class had to be made abstract as well. But later accidentally I added 2 other non-abstract methods within this same abstract class. So when I fixed this & re-build the project. DaggerAppComponent was available for import.
This is all what I have in my app level build.gradle file in relation to dagger :-
Within plugins block :
id 'kotlin-kapt'
Within dependencies block :
implementation "com.google.dagger:dagger:2.43.2"
kapt "com.google.dagger:dagger-compiler:2.43.2"
(Please note that "2.43.2" was the latest available version at the time of posting this comment)
My failed attempts (These answers didn't work for me) :-
Clean > Re-build > Invalidates caches & restart.
Switching to Project view, deleting build folder & re-building the project.
Trying out various dagger versions & extra dagger dependencies that I didnt require.

Dagger doesn't generate Adapter for TestModule

How can I make apt process test source files?
When I have following setup I'm getting exception:
java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.module.TestDatabaseModule could not be loaded. Please ensure that code generation was run for this module.
and class TestDatabaseModule$$ModuleAdapter is not generated while DatabaseModule$$ModuleAdapter is generated correctly.
What am I doing wrong here?
build.gradle:
apt {
arguments {
androidManifestFile variant.processResources.manifestFile
resourcePackageName 'info.korzeniowski.walletplus'
}
}
dependencies {
// Dagger
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
}
./src/main/java/info/korzeniowski/walletplus/module/DatabaseModule.java
package info.korzeniowski.walletplus.module;
#Module(
...
)
public class DatabaseModule {
private DatabaseHelper databaseHelper;
public DatabaseModule(WalletPlus application) {
databaseHelper = OpenHelperManager.getHelper(application, DatabaseHelper.class);
}
#Provides
#Singleton
public DatabaseHelper provideDatabaseHelper() {
return databaseHelper;
}
...
}
./src/androidTest/java/info/korzeniowski/walletplus/test/module/TestDatabaseModule.java
package info.korzeniowski.walletplus.test.module;
#Module(
...
includes = DatabaseModule.class,
overrides = true
)
public class TestDatabaseModule {
#Provides
#Singleton
public DatabaseHelper provideTestDatabaseHelper() {
return new DatabaseHelper(Robolectric.application, null);
}
}
RobolectricTest.java
#Before
public void setUp() {
List<Object> modules = new ArrayList<Object>();
modules.add(new DatabaseModule((WalletPlus) Robolectric.application));
modules.add(new TestDatabaseModule());
ObjectGraph.create(modules.toArray()).inject(this);
}
The apt configuration is only for the main sources. You must also declare the annotation processor to run for test sources since it is a separate invocation of javac.
You can do so using a similar syntax:
androidTestApt "com.squareup.dagger:dagger-compiler:$daggerVersion"

Overriding debug module in tests

I have a Gradle app with a project structure similar to Jake Wharton's u2020:
/src
/androidTest
/debug
/main
/release
In my application class I build the Dagger graph and inject it:
MyApplication extends Application {
...
public void buildObjectGraphAndInject() {
Object[] modules = Modules.list(this);
mApplicationGraph = ObjectGraph.create(modules);
mApplicationGraph.inject(this);
}
...
}
Inside the debug sourceset, I define Modules.list() as:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new DebugAppModule()
};
}
private Modules() {
// No instances.
}
}
Inside the release sourceset, I define the same thing minus the DebugAppModule:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app)
};
}
private Modules() {
// No instances.
}
}
Deeper down my dependency graph, I create a MockRestAdapter that I can use when running the debug version:
#Module(
complete = false,
library = true,
overrides = true
)
public final class DebugApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
AndroidMockValuePersistence.install(mockRestAdapter, preferences);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(RestAdapter restAdapter, MockRestAdapter mockRestAdapter,
#IsMockMode boolean isMockMode, MockMyApi mockService) {
if (isMockMode) {
return mockRestAdapter.create(MyApi.class, mockService);
}
return restAdapter.create(MyApi.class);
}
}
But while I'm running tests, I would like to override the DebugApiModule with a TestApiModule that looks like this:
#Module(
complete = false,
library = true,
overrides = true
)
public final class TestApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
mockRestAdapter.setDelay(0);
mockRestAdapter.setErrorPercentage(0);
mockRestAdapter.setVariancePercentage(0);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(MockRestAdapter mockRestAdapter, MockHnApi mockService) {
return mockRestAdapter.create(MyApi.class, mockService);
}
}
What's the best way to accomplish this? Do I need to create a TestAppModule like this:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new TestAppModule()
};
}
private Modules() {
// No instances.
}
}
And replace all of the DebugFooModule with TestFooModules? If so, how do I get around the fact that Modules.java is duplicated? Or am I way off base?
EDIT: SOLUTION
What I ended up doing is replacing the Application-level graph (where the MockRestAdapter gets created) during my test setUp
protected void setUp() throws Exception {
super.setUp();
HnApp app = HnApp.getInstance();
app.buildObjectGraphAndInject(TestModules.list(app));
getActivity();
}
What I've done this far (and I'm not entirely sure this will scale yet) is create a static test module class in my tests.
#Module(
injects = {
SharedPreferences.class
})
static class TestModule {
#Provides #Singleton SharedPreferences provideSharedPreferences(){
return Mockito.mock(SharedPreferences.class);
}
}
And in the setup() method I inject the test module
#Before
public void setUp(){
ObjectGraph.create(new TestModule()).inject(this);
}
I then #Inject the mocked class:
#Inject SharedPreferences sharedPreferences;

Dagger can't find injectable members on a module

I'm using Dagger for dependency injection in an Android project, and can compile and build the app fine. The object graph appears to be correct and working, but when I add dagger-compiler as a dependency to get errors at compile time, it reports some bizarre errors:
[ERROR] error: No binding for com.squareup.tape.TaskQueue<com.atami \
.mgodroid.io.NodeIndexTask> required by com.atami \
.mgodroid.ui.NodeIndexListFragment for com.atami.mgodroid \
.modules.OttoModule
[ERROR] error: No binding for com.squareup.tape.TaskQueue<com.atami \
.mgodroid.io.NodeTask> required by com.atami \
.mgodroid.ui.NodeFragment for com.atami.mgodroid.modules.OttoModule
[ERROR] error: No injectable members on com.squareup.otto.Bus. Do you want
to add an injectable constructor? required by com.atami. \
mgodroid.io.NodeIndexTaskService for
com.atami.mgodroid.modules.TaskQueueModule
The Otto error looks like the one Eric Burke mentions in his Android App Anatomy presentation about not having a #Provides annotation, but as you can see below I do.
My Otto and TaskQueue modules are as follows:
#Module(
entryPoints = {
MGoBlogActivity.class,
NodeIndexListFragment.class,
NodeFragment.class,
NodeActivity.class,
NodeCommentFragment.class,
NodeIndexTaskService.class,
NodeTaskService.class
}
)
public class OttoModule {
#Provides
#Singleton
Bus provideBus() {
return new AsyncBus();
}
/**
* Otto EventBus that posts all events on the Android main thread
*/
private class AsyncBus extends Bus {
private final Handler mainThread = new Handler(Looper.getMainLooper());
#Override
public void post(final Object event) {
mainThread.post(new Runnable() {
#Override
public void run() {
AsyncBus.super.post(event);
}
});
}
}
}
...
#Module(
entryPoints = {
NodeIndexListFragment.class,
NodeFragment.class,
NodeIndexTaskService.class,
NodeTaskService.class
}
)
public class TaskQueueModule {
private final Context appContext;
public TaskQueueModule(Context appContext) {
this.appContext = appContext;
}
public static class IOTaskInjector<T extends Task>
implements TaskInjector<T> {
Context context;
/**
* Injects Dagger dependencies into Tasks added to TaskQueues
*
* #param context the application Context
*/
public IOTaskInjector(Context context) {
this.context = context;
}
#Override
public void injectMembers(T task) {
((MGoBlogApplication) context.getApplicationContext())
.objectGraph().inject(task);
}
}
public static class ServiceStarter<T extends Task>
implements ObjectQueue.Listener<T> {
Context context;
Class<? extends Service> service;
/**
* Starts the provided service when a Task is added to the queue
*
* #param context the application Context
* #param service the Service to start
*/
public ServiceStarter(Context context,
Class<? extends Service> service) {
this.context = context;
this.service = service;
}
#Override
public void onAdd(ObjectQueue<T> queue, T entry) {
context.startService(new Intent(context, service));
}
#Override
public void onRemove(ObjectQueue<T> queue) {
}
}
#Provides
#Singleton
TaskQueue<NodeIndexTask> provideNodeIndexTaskQueue() {
ObjectQueue<NodeIndexTask> delegate =
new InMemoryObjectQueue<NodeIndexTask>();
TaskQueue<NodeIndexTask> queue = new TaskQueue<NodeIndexTask>(
delegate, new IOTaskInjector<NodeIndexTask>(appContext));
queue.setListener(new ServiceStarter<NodeIndexTask>(
appContext, NodeIndexTaskService.class));
return queue;
}
#Provides
#Singleton
TaskQueue<NodeTask> provideNodeTaskQueue() {
ObjectQueue<NodeTask> delegate =
new InMemoryObjectQueue<NodeTask>();
TaskQueue<NodeTask> queue = new TaskQueue<NodeTask>(
delegate, new IOTaskInjector<NodeTask>(appContext));
queue.setListener(new ServiceStarter<NodeTask>(
appContext, NodeTaskService.class));
return queue;
}
}
...
/**
* Module that includes all of the app's modules. Used by Dagger
* for compile time validation of injections and modules.
*/
#Module(
includes = {
MGoBlogAPIModule.class,
OttoModule.class,
TaskQueueModule.class
}
)
public class MGoBlogAppModule {
}
Dagger's full graph analysis works from a complete module. i.e. #Module(complete = true), which is the default. Because it's the default, dagger will, by default, assume that all bindings are available from that module or those modules it includes explicitly.
In this case, you've given two modules that you claim are complete, but Dagger has no way to tie these together at compile time without an additional signal. In short, without OttoModule knowing about TaskQueueModule, the compiler will attempt to analyse OttoModule for its claimed completeness, and fail, because it doesn't now about TaskQueueModule.
Modify OttoModule's annotation as such:
#Module(
includes = TaskQueueModule.class,
entryPoints = {
MGoBlogActivity.class,
NodeFragment.class,
NodeActivity.class,
NodeCommentFragment.class,
NodeIndexTaskService.class,
NodeTaskService.class
}
)
and then Dagger will know that for OttoModule to be complete, it includes the other module as part of its full definition.
Note:dagger-compiler can't detect that TaskQueueModule is there on the class path and just "know" that the developer intended it to be used with OttoModule without that additional signal. For instance, you might have several modules which define task queues and which one would it select? The declaration must be explicit.

Categories

Resources