Android Test with Dagger mock inject constructor - android

Hi i've got a following Problem. I want to write android tests with espresso for the Ui and in order to have tests that are not flaky i want to mock my presenter.
I use Dagger in the App. My Configuration is as Following:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
//some injections
//some providings
}
I have a Module for the Component
#Module
public class AppModule {
//providings for component
}
then i have also a component for the activities with a module for the component
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//inject activites
//provide subcomponents for activites
}
then i have subcomponents for my pages
#PerActivity
#Subcomponent(modules = InfoModule.class)
public interface InfoComponent {
void inject(DetailActivity activity);
}
and a module for the subcomponent
#Module
public class InfoModule {
#Provides
public DetailPresenter provideDetailPresenter(ShowDetailsUseCase showDetailsUseCase,
OtherUseCase getPoisUseCase,
AccountManager accountManager, Navigator
navigator) {
return new DetailPresenter(showDetailsUseCase, otherUseCase, accountManager, navigator);
}
}
and then the detail Activity Injects the DetailPresenter
public class DetailActivity extends BaseActivity {
#Inject
DetailPresenter mPresenter;
InfoComponent mComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mComponent = getActivityComponent().provideInfoModule(new InfoModule());
mComponent.inject(this);
mPresenter.bindView(this);
mPresenter.onCreate(new PresenterBundle(getIntent().getExtras(), savedInstanceState));
}
//functionality of detailActiviy
}
then i have the presenter which uses constructor injection
public class DetailPresenter extends BasePresenter {
private ShowDetailsUseCase mDetailsUseCase;
private final OtherUseCase getPoisUseCase;
private AccountManager accountManager;
private Navigator navigator;
#Inject
public DetailPresenter(ShowDetailsUseCase getDetailsUseCase, OtherUseCase getPoisUseCase,
AccountManager
accountManager, Navigator navigator) {
this.mDetailsUseCase = getDetailsUseCase;
this.getPoisUseCase = gotherUseCase;
this.accountManager = accountManager;
this.navigator = navigator;
}
#Override
public void onCreate(#Nullable PresenterBundle bundle) {
super.onCreate(bundle);
//other things to do on initialization
((DetailView) getView()).showDetails(getDetailsFromUseCase());
}
}
Now in the test i want to do mock the presenter:
#RunWith(AndroidJUnit4.class)
public class DetailActivityTest {
#Rule
public final ActivityTestRule<DetailActivity> main = new ActivityTestRule<DetailActivity>(DetailActivity.class, true, false);
#Rule
public final DaggerMockRule<AppComponent> rule=new EspressoDaggerMockRule();
#Mock
DetailPresenter presenter; //does not work because #Inject constructor
#Test
public void locationTest() {
Details details = generateDetails();
launchActivity();
doAnswer(answer -> {
activity.showDetails(details);
return null;
}
).when(presenter).onCreate(any());
//espresso verify afterwards
}
}
but if i try to mock the following error shows:
java.lang.RuntimeException: Error while trying to override objects:
a.b.c.ui.mvp.presenter.DetailPresenter
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
does someone have an idea how I am able to mock the presenter even with #Inject constructor and dependencies.
I do not want to mock the data layer because then I have to mock database, apiClient, cacheData and so on. And some of the datalayer also have inject dependencies so i cannot mock them either.
Thank you in advance

The DetailPresenter class is created in the InfoModule, so you don't need the Inject annotation. The error you get is because using DaggerMock you can replace only the objects created in a module. In your example you are already creating it in a module, you just need to remove the Inject annotation.

Related

Field injection in unit tests with Dagger 2

As advised in Dagger documentation, for unit testing we don't have to involve Dagger at all, and for the provided example it makes sense:
class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
#Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
With this class structure it is simple to unit test, just mock the getter and putter, pass them as constructor arguments, instruct mockito what to return when interacting with any of these objects, and then make assertions on doTheThing(...).
Where I am struggling at testing is when I have to unit test a class with a structure similar to this:
class ThingDoer {
#Inject
ThingGetter getter;
#Inject
ThingPutter putter;
#Inject
ThingMaker maker;
#Inject
// other 10 objects
public ThingDoer() {
App.getThingComponent().inject(this);
}
String doTheThing(int howManyTimes) { /* … */ }
}
As you can see, I am no longer using constructor injection, but field injection instead. The main reason is not have too many parameters in the constructor.
Is there a way to inject mock dependencies in ThingDoer when all its dependencies are provided using field injections?
For field injection, you can create a component and a module which are used in unit test.
Suppose you have the unit test class ThingDoerTest, you can make the component injects dependencies to ThingDoerTest instead ThingDoer and the module provides the mock object instead real object.
In my project, HomeActivity has a field injection HomePresenter. Following code are some snippets. Hope the code can give you some idea.
#RunWith(AndroidJUnit4.class)
public class HomeActivityTest implements ActivityLifecycleInjector<HomeActivity>{
#Rule
public InjectorActivityTestRule<HomeActivity> activityTestRule = new InjectorActivityTestRule<>(HomeActivity.class, this);
#Inject
public HomePresenter mockHomePresenter;
#Override
public void beforeOnCreate(HomeActivity homeActivity) {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
MyApplication myApplication = (MyApplication) instrumentation.getTargetContext().getApplicationContext();
TestHomeComponent testHomeComponent = DaggerHomeActivityTest_TestHomeComponent.builder()
.appComponent(myApplication.getAppComponent())
.mockHomeModule(new MockHomeModule())
.build();
testHomeComponent.inject(HomeActivityTest.this);
homeActivity.setHomeComponent(testHomeComponent);
}
#Test
public void testOnCreate() throws Exception {
verify(mockHomePresenter).start();
}
#ActivityScope
#Component(
dependencies = {
AppComponent.class
},
modules = {
MockHomeModule.class
}
)
public interface TestHomeComponent extends HomeComponent {
void inject(HomeActivityTest homeActivityTest);
}
#Module
public class MockHomeModule {
#ActivityScope
#Provides
public HomePresenter provideHomePresenter() {
return mock(HomePresenter.class);
}
}
}

How to inject into a java class that doesn't have any activity or fragment using dagger2

Android Studio 2.2.2
I have a NewsListModelImp class which is the model in the MVP.
I want to inject my retrofit service into the model. However, as NewsListModelImp doesn't contain any reference to a context or activity I cannot call getApplication(). Which is what you would do if you were in a activity or fragment. I don't want to pass any context or activity in the constructor of NewsListModeImp as that would have to come from the presenter and I want to avoid any android stuff there.
public class NewsListModelImp implements NewsListModelContract {
#Inject
NYTimesSearchService mNYTimesSearchService;
public NewsListModelImp() {
((NYTimesSearchApplication)getApplication()).getAppComponent().inject(this);
}
}
My Application class
public class NYTimesSearchApplication extends Application {
private AppComponent mAppComponent;
public void onCreate() {
super.onCreate();
/* Setup dependency injection */
createAppComponent();
}
private void createAppComponent() {
mAppComponent = DaggerAppComponent
.builder()
.retrofitModule(new RetrofitModule())
.build();
}
public AppComponent getAppComponent() {
return mAppComponent;
}
}
My provides module
#Module
public class RetrofitModule {
private Retrofit retrofit() {
return new Retrofit
.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
#Provides
#Singleton
public NYTimesSearchService providesNYTimesSearch() {
return retrofit().create(NYTimesSearchService.class);
}
}
My Appcomponent
#Singleton
#Component(modules = {RetrofitModule.class})
public interface AppComponent {
void inject(NewsListModelImp target);
}
Many thanks for any suggestions,
Dagger-2 works reccurently. So if inside Activity (or Fragment) object is injected and it's constructor is properly annotated with #Inject annotation, the constructor's parameters will be injected too.
Suppose inside the application you would like to inject:
#Inject NyTimesPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((NYTimesSearchApplication) getApplication()).getAppComponent().inject(this);
}
The constructor of NyTimesPresenter must be annotated with #Inject:
public class NyTimesPresenter {
NewsListModelImp newsListModel;
#Inject
public NyTimesPresenter(NewsListModelImp newsListModel) {
this.newsListModel = newsListModel;
}
}
NewsListModelImp constructor must also be annotated with #Inject:
public class NewsListModelImp implements NewsListModelContract {
NYTimesSearchService mNYTimesSearchService;
#Inject
public NewsListModelImp(NYTimesSearchService nYTimesSearchService) {
this.mNYTimesSearchService = nYTimesSearchService;
}
}
Then everything will be injected properly.
Why the parameters should be passed to class as constructors' parameters? Such design pattern conforms SOLID principles. Object dependencies are injected into objects and not created inside it and such code is easily testable (in tests dependencies can be replaced ie. by Mock's)
EXTRA INFO:
It is possible to inject objects implementing specific interfaces. Such technique is described here. In your case NyTimesPresenter can have NewsListModelContract as it's dependency instead of NewsListModelImp. To do this add another module to your AppComponent:
#Singleton
#Component(
modules = {
RetrofitModule.class,
AppModule.class
})
public interface AppComponent {
AppComponent method to provide concrete class implementing interface should look like:
#Singleton
#Module
public abstract class AppModule {
#Binds
public abstract NewsListModelContract provideNewsListModelContract(NewsListModelImp newsListModelImp);
}
The implementation of NyTimesPresenter should change (just to replace concrete class with interface it implements):
public class NyTimesPresenter {
NewsListModelContract newsListModel;
#Inject
public NyTimesPresenter(NewsListModelContract newsListModel) {
this.newsListModel = newsListModel;
}
}

Dagger 2 error: "RepositoryImpl cannot be provided without an #Inject constructor or from an #Provides-annotated method"

For example, I have following interface:
public interface Repository {
Observable<Pojo> getPojos();
}
And its implementation:
public class RepositoryImpl implements Repository {
public RepositoryImpl() {
}
#Override
public Observable<Pojo> getPojos() {
return null;
}
}
Module:
#Module
class AppModule {
public AppModule() {
}
#Provides
#Singleton
Repository provideRepositoryImpl() {
return new RepositoryImpl();
}
}
And component:
#Singleton
#Component(modules = { AppModule.class })
public interface AppComponent {
void inject(MainActivity mainActivity);
}
When I trying to build project, I receive error as in question title. What problem in my code?
Read your error carefully (emphasis mine):
Dagger 2 error: “RepositoryImpl cannot be provided without an #Inject constructor or from an #Provides-annotated method”
Generally this means you've tried to #Inject RepositoryImpl, not #Inject Repository. This is especially important because your Module directly calls the RepositoryImpl constructor rather than letting Dagger create your RepositoryImpl using an #Inject-annotated constructor. (If you had, you could make RepositoryImpl a parameter of your #Provides method or switch to a #Binds method, and you have your choice between injecting the interface versus the implementation.)
The way I setup Dagger 2 is in my projects Application I add the injection component. like so.
public class NyApplication extends Application {
InjectionComponent component;
#Override
public void onCreate() {
super.onCreate();
setDagger();
}
private void setDagger() {
component = DaggerAppComponent.builder()
.appComponent(new AppModule())
.build();
component.inject(this);
}
public InjectionComponent getComponent() {
return component;
}}
and then int my activity whatever it is. I inject on it's onCreate like this.
public class MainActivity extends Activity {
#Inject
Object object;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).getComponent().inject(this);
}}
I hope this helps you.

Adding Non Activity Classes to Dagger 2 Graph Android

I'm having a hard time wrapping my head around how to use Dagger 2.0 outside of the limited examples I've seen. Let's take an example reading application. In this reading app, there is a library of a user's stories and the ability to Log in. The classes of interest for the purpose of this example are:
MainApplication.java - extends Application
LibraryManager.java - Manager which is responsible for adding/removing stories in the user's library. This is called from the MainApplication
AccountManager.java - Manager which is responsible for saving all a user's login information. It can be called from the LibraryManager
I'm still trying to wrap my head around what Components and Modules I should be creating. Here's what I can gather so far:
Create a HelperModule that provides an AccountManager and LibraryManager instance:
#Module
public class HelperModule {
#Provides
#Singleton
AccountManager provideAccountManager() {
return new AccountManager();
}
#Provides
#Singleton
LibraryManager provideLibraryManager() {
return new LibraryManager();
}
}
Create a MainApplicationComponent that lists the HelperModule in its list of modules:
#Singleton
#Component(modules = {AppModule.class, HelperModule.class})
public interface MainApplicationComponent {
MainApplication injectApplication(MainApplication application);
}
Include #Injects LibraryManager libraryManager in the MainApplication and inject the application into the graph. Finally it queries the injected LibraryManager for the number of stories in the library:
public class MainApplication extends Application {
#Inject LibraryManager libraryManager;
#Override
public void onCreate() {
super.onCreate();
component = DaggerMainApplicationComponent.builder()
.appModule(new AppModule(this))
.helperModule(new HelperModule())
.build();
component.injectApplication(this);
// Now that we have an injected LibraryManager instance, use it
libraryManager.getLibrary();
}
}
Inject the AccountManager into the LibraryManager
public class LibraryManager {
#Inject AccountManager accountManager;
public int getNumStoriesInLibrary() {
String username = accountManager.getLoggedInUserName();
...
}
}
However the problem is that the AccountManager is null when I try to use it in the LibraryManager and I don't understand why or how to solve the problem. I'm thinking that it's because the MainApplication that was injected into the graph doesn't use the AccountManager directly, but then do I need to inject the LibraryManager into the graph some how?
modify your classes as follow and it would work:
your POJO:
public class LibraryManager {
#Inject AccountManager accountManager;
public LibraryManager(){
MainApplication.getComponent().inject(this);
}
public int getNumStoriesInLibrary() {
String username = accountManager.getLoggedInUserName();
...
}
...
}
your component Interface:
#Singleton
#Component(modules = {AppModule.class, HelperModule.class})
public interface MainApplicationComponent {
void inject(MainApplication application);
void inject(LibraryManager lm);
}
}
your application Class :
public class MainApplication extends Application {
private static MainApplicationComponent component;
#Inject LibraryManager libraryManager;
#Override
public void onCreate() {
super.onCreate();
component = DaggerMainApplicationComponent.builder()
.appModule(new AppModule(this))
.helperModule(new HelperModule())
.build();
component.injectApplication(this);
// Now that we have an injected LibraryManager instance, use it
libraryManager.getLibrary();
}
public static MainApplicationComponent getComponent(){return component;}
}
In fact, you need to do the same for all of your dependent classes, basically you have access to the application class in all Activity sub-classes so making get component as an static method is none-less. but for POJO u need to catch the component somehow. there are a lot of way to implement. this is just an illustration to give u the idea how it works.
now you can destroy the mars :)
You can satisfy dependency directly in provide method:
#Provides
#Singleton
LibraryManager provideLibraryManager(AccountManager accountManager) {
return new LibraryManager(accountManager);
}
Or use constructor injection (remove provideLibraryManager() method from HelperModule):
#Signleton
public class LibraryManager {
private final AccountManager accountManager;
#Inject
public LibraryManager(AccountManager accountManager) {
this.accountManager = accountManager
}
public int getNumStoriesInLibrary() {
String username = accountManager.getLoggedInUserName();
...
}
}
Objects created with constructor injection are provided automatically.
If you have a lot of parameters in LibraryManager you can use method injection for setters in addition to constructor injection:
#Singleton
public class LibraryManager {
private final AccountManager accountManager;
private SomeManager someManager;
#Inject
public LibraryManager(AccountManager accountManager) {
this.accountManager = accountManager
}
#Inject
public setSomeManager(SomeManager someManager) {
this.someManager = someManager
}
public int getNumStoriesInLibrary() {
String username = accountManager.getLoggedInUserName();
...
}
}
Method injection is performed after object is instantiated. However, this use case of method injection is not valid, try to prefer constructor or field injection.
I think I've come up with a pretty good solution. Instead of trying to inject the AccountManager into the LibraryManager, I'm providing the AccountManager in the MainApplicationComponent and accessing from the LibraryManager that way.
MainApplicationComponent:
#Singleton
#Component(modules = {AppModule.class, HelperModule.class})
public interface MainApplicationComponent {
MainApplication injectApplication(MainApplication application);
// Provide the managers here so all classes that have a pointer to the MainApplicationComponent can access them.
// This avoids having to pass each manager to the constructor of all classes that need them
AccountManager accountManager();
ArchiveManager archiveManager();
}
Using the sample Android App for inspiration (https://github.com/gk5885/dagger-android-sample) I've created a HasComponent interface:
public interface HasComponent<C> {
C getComponent();
}
and made the MainApplication implement the interface. Also when creating the HelperModule you'll notice it passes this so the module can access the component:
public class MainApplication extends Application implements HasComponent<MainApplicationComponent>{
MainApplicationComponent mainApplicationComponent;
#Override
public MainApplicationComponent getComponent() {
return mainApplicationComponent;
}
#Override
public void onCreate() {
super.onCreate();
component = DaggerMainApplicationComponent.builder()
.appModule(new AppModule(this))
.helperModule(new HelperModule(this))
.build();
component.injectApplication(this);
// Now that we have an injected LibraryManager instance, use it
mainApplicationComponent.libraryManager().getLibrary();
}
}
The LibraryManager is changed so it takes the HasComponent in as a parameter in the constructor:
public class LibraryManager {
AccountManager accountManager;
public ArchiveManager(HasComponent<MainApplicationComponent> hasComponent) {
accountManager = hasComponent.getComponent().accountManager();
}
...
}
and finally in the HelperModule we just pass the implementation of HasComponent<MainApplicationComponent> to the LibraryManager's constructor:
#Module
public class HelperModule {
private HasComponent<WattpadComponent> hasComponent;
public HelperModule(HasComponent<WattpadComponent> hasComponent) {
this.hasComponent = hasComponent;
}
#Provides
#Singleton
AccountManager provideAccountManager() {
return new AccountManager(hasComponent);
}
#Provides
#Singleton
ArchiveManager provideLibraryManager() {
return new LibraryManager(hasComponent);
}
}
This should also make it really easy for unit testing. If I am unit testing the LibraryManager and want to mock out the AccountManager I can simply create a TestMainApplicationComponent that extends MainApplicationComponent and includes a TestHelperModule in it's list of modules which will provide a mocked AccountManager and pass the TestMainApplicationComponent to the LibraryManager's constructor.
I'm new to Dagger so I might be missing something but I've tried out everything but the unit testing and it seems to be working so far. Will post a GitHub link shortly with unit testing examples for those interested.
Thanks to #Kirill's answer for a better understanding of how the Components instantiate the objects.

Dagger v2: Inject 2 different scopes into one object

I have moduleA setup as an application wide singleton provider, ModuleB as a user related object provider
My user display fragment will use system wide bus to send message to others and use user related object to display.
Problem cannot inject different scrope class into one object. Use component.getX method works fine, but inject is prefered way.
Error message:
#UserScope may not reference bindings with difference scopes: #Provides #Singleton Bus ModuleA.provideBus()
#Module
public class ModuleA {
#Provides #Singleton Bus provideBus() {...}
}
Module B as user related Info provider
#Module
public class ModuleB{
private final User user;
public ModuleB(User user) {...}
#Provides #UserScope User provideUser() {}
#Provides #UserScope UserManager provideUserManager() {}
}
Components setup like following:
#Component (modules={ModuleA.class})
#Singleton
public interface ComponentA {
Bus getBus();
void inject(ClassA target);
}
#Component(modules={ModuleB.class})
#UserScope
public interface ComponentB {
User getUser();
UserManager getUserManager();
void inject(ClassA target);
}
class UserFragment exrtends Fragment {
#Inject Bus bus;
#Inject UserManager userManager;
public void onCreate() {
getComponentA().inject(this);
getComponentB().inject(this);
}
}
Try this configuration, it works for me. There is really a lack of good documentation about Dagger2 so I studied a few open-source examples of code that you can find in GitHub etc by keyword like Dagger2.
Application level Component
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
// exported for child-components
Bus eventBus();
}
Application level Module
#Module
public class AppModule {
#Provides #Singleton
Bus provideBus() {
return BusProvider.getInstance();
}
}
Activity level Component
#ActivityScope
#Component(dependencies=AppComponent.class, modules=MainActivityModule.class)
public interface MainActivityComponent {
void inject( MainActivity mainActivity );
}
Activity level Module
#Module
public class MainActivityModule {
private final MainActivity mActivity;
public MainActivityModule( MainActivity activity ) {
mActivity = activity;
}
#Provides
MainActivityTitleController provideTitleController() {
return new MainActivityTitleController( mActivity );
}
}
Android Application class
public class MyApplication extends Application {
private AppComponent mAppComponent;
#Override
public void onCreate() {
super.onCreate();
// Dagger2
mAppComponent = Dagger_AppComponent.builder()
.appModule( new AppModule( this ))
.build();
}
public AppComponent getComponent() {
return mAppComponent;
}
public static AppComponent getComponent( Context context ) {
return ((MyApplication)context.getApplicationContext()).getComponent();
}
}
And finally Activity
public class MainActivity extends ActionBarActivity {
// Injectable fields
#Inject Bus mEventBus;
#Inject MainActivityTitleController mTitleController;
private MainActivityComponent mComponent;
#Override
protected void onCreate( Bundle savedInstanceState ) {
// Dagger2
mComponent = Dagger_MainActivityComponent.builder()
.appComponent( ((MyApplication)getApplication()).getComponent() )
.mainActivityModule( new MainActivityModule( this ) )
.build();
mComponent.inject( this );
}
}
I think the main problem in your code snippets you provided, is that your ModuleB should have a dependency on ModuleA to correctly provide the singleton with the error you were getting. I.e. this should work:
#Component(modules={ModuleB.class}, dependencies = ComponentA.class)
#UserScope
public interface ComponentB {
User getUser();
UserManager getUserManager();
void inject(MainActivity target);
}
I recreated your classes and made some assumptions to fill in the blanks, and it seems to work fine. You can see the full working code here on GitHub. The only difference in my code is, what you called ClassA/UserFragment I just called MainActivity but otherwise the structure is the same.

Categories

Resources