Mocking dependency not listed in module - android

I am using very simple and likely very common scenario. Here is my sample dependency:
public class MyDependency {
#Inject
public MyDependency(...) {
...
}
}
I am not listing the above in any module (that is, there is no #Provides for MyDependency).
My sample use case goes like this:
public class ThePresenter {
#Inject
MyDependency myDependency;
public ThePresenter() {
App.getInstance().getAppComponent().inject(this);
}
}
Now I'd like to mock my dependency in unit tests. I don't want to use modules overrides (that would mean I have to add #Provides for all my dependencies marked with #Inject constructors), test components etc. Is there any alternative but standard and simple approach for the problem?

You need to use constructor injection, rather than your injection site inside the Presenter class constructor. Expose your Presenter to dagger2 by adding the #Inject annotation on the constructor (like you have done with the dependency):
public class ThePresenter {
private final MyDependency myDependency;
#Inject public ThePresenter(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
This then allows inversion of control and supplying the dependency/mock.
Usage :
public class ThePresenterTest {
#Mock private MyDependency myDependency;
private ThePresenter presenter;
#Before public void setup() {
MocktioAnnotations.initMocks(this);
presenter = new ThePresenter(myDependency);
Mockito.when(myDependency.someMethod()).thenReturn(someValue);
....
}
}

Just mock it?
public class ThePresenterTest {
#Mock MyDependency myDependency;
private ThePresenter presenter;
#Before
public void setup() {
initMocks(this);
presenter = new ThePresenter();
}
}

Related

Dagger code works, how to make that Toothpick code work the same way?

I am starting using Toothpick and I need to migrage from Dagger code to Toothpick.
Well, I have next string at my project with Dagger.
#Provides
#Singleton
#Named(ProjectsRepository.DB)
ProjectsRepository provideProjectsDBRepository(ProjectsDBRepository repository) {
return repository;
}
and
#Provides
#Singleton
ProjectsService provideProjectsService(ProjectsServiceImpl serviceImplementation) {
return serviceImplementation;
}
and
#Inject
#Named(ProjectsRepository.SERVER)
ProjectsRepository mServerRepository;
#Inject
#Named(ProjectsRepository.DB)
ProjectsRepository mDBRepository;
#Inject
public ProjectsServiceImpl() {
}
It works fine (beside what I still can't understand what or who put repository/serviceImpelmentation to provide-methods) .
Well, I am trying to do the same in Toothpick and do the next:
public RepositoryModule() {
bind(ProjectsRepository.class).withName(ProjectsServer.class).toInstance(new ProjectsServerRepository());
}
then
public ServiceModule() {
bind(ProjectsService.class).toInstance(new ProjectsServiceImpl());
}
and finally
#Inject
#ProjectsServer
ProjectsRepository mServerRepository;
#Inject
#ProjectsDatabase
ProjectsRepository mDBRepository;
#Inject
public ProjectsServiceImpl() {
}
and I have all Qualifiers above.
I install my modules in the Fragment class (Fragment Scope) next way
Scope currentScope = Toothpick.openScopes(ApplicationInstance.class,ProjectsFragment.class);
currentScope.installModules(
new ServiceModule(),
new RepositoryModule());
Toothpick.inject(this, currentScope);
But in the end I am getting NullPointerException that my ProjectsRepository is null, and I can't use method of it's instance.
Where am I making mistakes? Please help to "translate".
Well, I just had to use Providers for my goals. Can't say that it is very transparent for using.
Thanks to all.
Providers can be used in the next way (show only for Server Repository)
First, ServiceModule
public ServiceModule() {
bind(ProjectsService.class).toProvider(ProjectsServiceImplProvider.class);
}
Second, RepositoryModule
public RepositoryModule() {
bind(ProjectsRepository.class).withName(ProjectsServer.class).toProvider(ProjectsServerRepositoryProvider.class);
}
Third, ServiceImplProvider
public class ProjectsServiceImplProvider implements Provider<ProjectsServiceImpl> {
#Inject
#ProjectsServer
ProjectsRepository mServerRepository;
#Inject
#ProjectsDatabase
ProjectsRepository mDBRepository;
#Override
public ProjectsServiceImpl get() {
return new ProjectsServiceImpl(mServerRepository, mDBRepository);
}
}
And the last - RepositoryProvider (Api or DB - depends on goals)
public class ProjectsServerRepositoryProvider implements Provider<ProjectsServerRepository> {
#Inject
SomeApi mApi;
#Override
public ProjectsServerRepository get() {
return new ProjectsServerRepository(mApi);
}
}

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;
}
}

Android Test with Dagger mock inject constructor

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.

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.

Categories

Resources