Problem Description
I'm trying to mock object creation inside of method.
I have LoginFragment which is creating LoginPresenterImpl inside of onCreate method, like shown below:
public class LoginFragment extends BaseFragment {
private LoginPresenter mPresenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = new LoginPresenterImpl(this); <<-- Should be mocked
}
}
I have some problems with combining RobolectricGradleTestRunner and PowerMockRunner in one test but after reading this post, I found way how to do that, so my test look like this:
BaseRobolectricTest.java
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
#PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
public abstract class BaseRobolectricTest {
}
Test.java
#PrepareForTest({LoginPresenterImpl.class})
public class Test extends BaseRobolectricTest {
private LoginPresenterImpl mPresenterMock;
#Rule
public PowerMockRule rule = new PowerMockRule();
#Before
public void setup() {
mockStatic(LoginPresenterImpl.class);
mPresenterMock = PowerMockito.mock(LoginPresenterImpl.class);
}
#Test
public void testing() throws Exception {
when(mPresenterMock.loadUsername(any(Context.class))).thenReturn(VALID_USERNAME);
when(mPresenterMock.loadPassword(any(Context.class))).thenReturn(VALID_PASSWORD);
when(mPresenterMock.canAutologin(VALID_USERNAME, VALID_PASSWORD)).thenReturn(true);
whenNew(LoginPresenterImpl.class).withAnyArguments().thenReturn(mPresenterMock);
FragmentTestUtil.startFragment(createLoginFragment());
}
private LoginFragment createLoginFragment() {
LoginFragment loginFragment = LoginFragment.newInstance();
return loginFragment;
}
}
This is just a bad code and opposite of what is "Dependency Injection" pattern.
If you used dependency injection framework like Dagger, such problem wouldn't happen as all used classed would be injected.
In test cases you would override your modules to provide mocks instead of real classes:
#Module
public class TestDataModule extends DataModule {
public TestDataModule(Application application) {
super(application);
}
#Override
public DatabaseManager provideDatabaseManager(DatabaseUtil databaseUtil) {
return mock(DatabaseManager.class);
}
}
And just mock their behavior.
SOLID rules are really important in mainaining testable and reusable code.
This might work, I have no way to test it though...
public class LoginFragment extends BaseFragment {
private LoginPresenter mPresenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = getLoginPresenter();
}
protected LoginPresenter getLoginPresenter() {
return new LoginPresenterImpl(this);
}
}
Then in your Test.java
private LoginFragment createLoginFragment() {
LoginFragment loginFragment = LoginFragmentTest.newInstance();
return loginFragment;
}
private static class LoginFragmentTest extends LoginFragment {
#Override
protected LoginPresenter getLoginPresenter() {
return mPresenterMock;
}
}
Firstly if you cannot change this source code my answer will not help and you have to return to heavy mocking tools.
From coding style and design perspective I would recommend to define a factory that creates an instance of LoginPresenter. You can ask for this factory in LoginFragment constructor. And then use this factory in onCreate method.
Then you can use your own implementation of this factory in unit tests that will create test implementation of LoginPresenter.
That is a POJO approach that makes your code testable.
For example
public class LoginFragment extends BaseFragment {
private LoginPresenter mPresenter;
private final LoginPresenterFactory presenterFactory;
public LoginFragment(LoginPresenterFactory presenterFactory) {
this.presenterFactory = presenterFactory;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = presenterFactory.create();
}
}
I assume that you can not change the production code.
so due to this bad design it is difficult achieve your requirement using a proper way.
But there is a dirty way to do this,
use reflection to assign value to the private field.
public class ReflectionUtility
{
public static void setValue(Object obj, String fieldName, Object value)
{
try
{
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
catch (NoSuchFieldException e)
{
throw new RuntimeException(e);
}
}
}
then in your test,
private LoginFragment createLoginFragment()
{
LoginFragment loginFragment = LoginFragment.newInstance();
ReflectionUtility.setValue(loginFragment, "mPresenter", mPresenterMock);
return loginFragment;
}
Related
I am sort of new to Dagger and still learning it. According to the tutorials and blogs I read, currently Android does not have a way of injecting dependencies into ViewModels hence we need to use a custom ViewModelProvider.Factory, that said, I managed to get my hands on this one
public class ViewModelProviderFactory implements ViewModelProvider.Factory {
private static final String TAG = ViewModelProviderFactory.class.getSimpleName();
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public ViewModelProviderFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It works, it has worked for many of my use cases until now. Originally I had to an instance of a ViewModel with something like this
public AFragment extends BaseFragment{
#Inject
ViewModelProviderFactory providerFactory;
private MyViewModel viewModel;
MyViewModel getViewModel(){
return ViewModelProviders.of(this, providerFactory).get(MyViewModel.class);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = getViewModel();
tokenAuthenticator.setAuthenticatorListener(this);
}
}
But as the project grew I realized this was not neat, I had to do this in all my fragments so I opted for a different approach, I wanted to instantiate my ViewModel in my BaseFragment instead and I did this
public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
private static final String TAG = BaseFragment.class.getSimpleName();
public E binding;
public final CompositeDisposable disposables = new CompositeDisposable();
public T viewModel;
#Inject
ViewModelProviderFactory providerFactory;
private int layoutId;
/**
* #return view model instance
*/
public T getViewModel() {
final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
}
}
This gives me compile error
A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeDeliveryPlanFragment.DeliveryPlanFragmentSubcomponent
.
.
.
A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributePayWithMtmMobileMoneyFragment.PayWithMtmMobileMoneyFragmentSubcomponent
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
xxx.xxx.core.base.ViewModelProviderFactory(creators)
xxx.xxx.core.base.ViewModelProviderFactory is injected at
mika.e.mikaexpressstore.core.base.BaseFragment.providerFactory
xxx.xxx.xxx.xxx.cashondelivery.CashOnDeliveryFragment is injected at
dagger.android.AndroidInjector.inject(T) [xxx.xxx.core.base.dagger.component.AppComponent → xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeCashOnDeliveryFragment.CashOnDeliveryFragmentSubcomponent]
2 errors
From the error message I can tell Dagger is complaining the ViewModelProviderFactory is being injected in the base but used in the child, I need help, is there a way to make this work? surely I want to reduce on boilerplate and repetitive code.
I finally fixed it, not how I wanted but better than having to instantiate each viewmodel from the child class. After reading this answer I came to a realization that this was not possible, so instead I removed #Inject annotation from ViewModelProviderFactory in my BaseFragment and it looked like
public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
private static final String TAG = BaseFragment.class.getSimpleName();
public E binding;
public final CompositeDisposable disposables = new CompositeDisposable();
public T viewModel;
#Inject
private ViewModelProviderFactory providerFactory;
private int layoutId;
/**
* #return view model instance
*/
public T getViewModel() {
final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
}
#MainThread
protected final void setProviderFactory(ViewModelProviderFactory providerFactory) {
this.providerFactory = providerFactory;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = getViewModel();
}
}
And injected the provider from the child fragments instead then called the setter from the BaseFragment
public AFragment extends BaseFragment{
#Inject
ViewModelProviderFactory providerFactory;
private MyViewModel viewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
setLayoutId(R.layout.layout_layout);
setProviderFactory(providerFactory);
super.onCreate(savedInstanceState);
}
}
Key here is to call setProviderFactory(provider) before super.onCreate(savedInstanceState) because when super onCreate is called the provider should not be null, it should be set and ready to create the ViewModel
I am using Dagger2 for dependency injection in quite a large project.
There are 2 different sections of the app that each use multiple usecases.
For each section I have a module that provides the dependencies for that section. I have come across a scenario where I need to use a usecase from moduleA in ModuleB and I am not sure how to include it or add it to be used for that one scenario.I have looked at other answers on stackoverflow but have not found one that answers my question.
For example this answer here
does not help me because it's setup is different and I cannot deduce from this solution how to solve my problem.
and here
I am not certain that I should make any of these dependencies into Singletons.
for example I have ModuleA below that contains AddItem that I need in ModuleB PayForItem
#Module
public class ModuleA {
private final FragmentActivity mActivity;
public ModuleA(FragmentActivity fragmentActivity) {
mActivity = fragmentActivity;
}
...
#Provides
AddItem addItemPresenter(DateMapper dataMapper, ItemdetailUseCase itemDetailUsecase){
return new AddItem(dataMapper,itemDetailUsecase);
}
#Module
public class ModuleB {
private final FragmentActivity mActivity;
public ModuleB(FragmentActivity fragmentActivity) {
mActivity = fragmentActivity;
}
...
#Provides
PayForItem payForItemPresenter(AddItem addItem){
return new AddItem(addItem);
}
Each module is included in its own subcomponent eg:
#Subcomponent(modules = {ModuleA.class})
public interface ModuleAPresentationComponent {
void inject(Fragment testFragment;
}
#Subcomponent(modules = {ModuleB.class})
public interface ModuleBPresentationComponent {
void inject(Fragment testFragment;
}
Both are included in the main component eg:
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
public ModuleAPresentationComponent newPresentationComponent(ModuleAPresentationModule presentationModule);
public ModuleBPresentationComponent newPresentationComponent(ModuleBPresentationModule presentationModule);
}
I have a BaseFragment that I use to inject eg:
public abstract class BaseFragment extends Fragment {
private boolean mIsInjectorUsed;
protected static final String PARAM_USER_ID = "param_user_id";
private ApplicationComponent getApplicationComponent() {
return ((AndroidApplication) getActivity().getApplication()).getApplicationComponent();
}
#UiThread
protected ModuleAPresentationComponent getModuleAPresentationComponent() {
if (mIsInjectorUsed) {
throw new RuntimeException("there is no need to use injector more than once");
}
mIsInjectorUsed = true;
return getApplicationComponent()
.newPresentationComponent(new ModuleAPresentationModule(getActivity()));
}
#UiThread
protected ModuleBPresentationComponent getModuleBPresentationComponent() {
if (mIsInjectorUsed) {
throw new RuntimeException("there is no need to use injector more than once");
}
mIsInjectorUsed = true;
return getApplicationComponent()
.newPresentationComponent(new ModuleBPresentationModule(getActivity()));
}
So in my fragment it injects like this:
public class testFragment extends BaseFragment{
#Inject AddItem addItemPresenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getModuleAPresentationComponent().inject(this);
}
}
How would I use AddItem from ModuleA in PayForItem ModuleB without repeating those dependencies in ModuleB?
Sorry for my english, now i begin learn dagger2 and i cant understand why i have error:
Error:(9, 10) error:
test.dagger.dagger.modules.MainActivityPresenterModule cannot be
provided without an #Inject constructor or from an #Provides- or
#Produces-annotated method.
test.dagger.dagger.modules.MainActivityPresenterModule is injected at
test.dagger.view.activitys.MainActivity.mainActivityPresenterModule
test.dagger.view.activitys.MainActivity is injected at
test.dagger.dagger.components.AppComponent.injectMainActivity(mainActivity)
App
public class App extends Application {
private static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return component;
}
}
AppComponent
#Component(modules = {MainActivityPresenterModule.class})
public interface AppComponent {
void injectMainActivity(MainActivity mainActivity);
}
MainActivityPresenterModule
#Module
public class MainActivityPresenterModule {
#Provides
MainActivityPresenter provideActivityPresenter(NetworkUtils networkUtils) {
return new MainActivityPresenter(networkUtils);
}
#Provides
NetworkUtils provideNetworkUtils(){
return new NetworkUtils();
}
}
NetworkUtils
public class NetworkUtils {
public boolean isConnection() {
return true;
}
}
MainActivityPresenter
public class MainActivityPresenter {
NetworkUtils networkUtils;
public MainActivityPresenter(NetworkUtils networkUtils) {
this.networkUtils = networkUtils;
}
public void getUser(){
if(networkUtils.isConnection()) {
Log.e("getUser", "getUser");
} else {
Log.e("no internet", "no internet connection");
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
MainActivityPresenterModule mainActivityPresenterModule;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
App.getComponent().injectMainActivity(MainActivity.this);
}
}
You can inject only the things that are provided in the classes annotated with #Module (only methods inside that module which are annotated with #Provides). So, you can do #Inject MainActivityPresenter presenter, for instance, not try to inject the whole module, like you tried to do. Modules should be registered on Dagger initialisation, like this (in App#onCreate)
component = DaggerAppComponent.builder()
.mainActivityPresenterModule(MainActivityPresenterModule())
.build()
In MainActivity you only need to call inject to be able to inject your #Inject MainActivityPresenter presenter or any other injects defined in the module, like so:
#Inject MainActivityPresenter presenter
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
(application as App).component.inject(this)
// after #inject(this) above you can start using your injections:
presenter.getUser()
}
Sorry, I wrote code snippets in Kotlin as it was much less to write that way, hopefully you get the idea how it looks in Java.
I just started using Dagger 2 and I found online thousands guides each one with a different implementation and I'm a bit confused now.
So basically this is what I wrote at the moment:
AppModule.java:
#Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
#Provides
#Singleton
Application providesApplication() {
return mApplication;
}
}
DataModule.java:
#Module
public class DataModule {
private static final String BASE_URL = "http://beta.fridgewizard.com:9001/api/";
#Provides
#Singleton
NetworkService provideNetworkService() {
return new NetworkService(BASE_URL);
}
#Provides
#Singleton
SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
}
PrefsModel.java:
#Module(includes = DataModule.class)
public class PrefsModel {
#Provides
#Singleton
QueryPreferences provideQuery(SharedPreferences prefs) {
return new QueryPreferences(prefs);
}
}
AppComponent.java (I'm exposing QueryPreferences object since I need it in a presenter, hopefully is correct in this way):
#Singleton
#Component(modules = {AppModule.class, DataModule.class, PrefsModel.class})
public interface AppComponent {
void inject(HomeFragment homeFragment);
QueryPreferences preferences();
NetworkService networkService();
}
Then I have the FwApplication.java:
public class FwApplication extends Application {
private static final String TAG = "FwApplication";
private NetworkService mNetworkService;
private AppComponent mDataComponent;
#Override
public void onCreate() {
super.onCreate();
buildComponentAndInject();
}
public static AppComponent component(Context context) {
return ((FwApplication) context.getApplicationContext()).mDataComponent;
}
public void buildComponentAndInject() {
mDataComponent = DaggerComponentInitializer.init(this);
}
public static final class DaggerComponentInitializer {
public static AppComponent init(FwApplication app) {
return DaggerAppComponent.builder()
.appModule(new AppModule(app))
.dataModule(new DataModule())
.build();
}
}
}
Finally I added another module for the presenters:
#Module
public class PresenterModule {
#Provides
Presenter<FwView> provideHomePresenter(NetworkService networkService) {
return new HomePresenterImpl(networkService);
}
#Provides
Presenter<FwView> provideSearchPresenter(NetworkService networkService) {
return new SearchPresenterImpl(networkService);
}
}
And the following component (which returns error because I cannot add a scoped dependencies here):
#Component(dependencies = AppComponent.class, modules = PresenterModule.class)
public interface PresenterComponent {
void inject(HomePresenterImpl presenter);
}
So, I have few questions that are not clear for me reading the documentation online:
How can I fix the error in the presenter component since it depends on NetworkService which is a singleton defined in the AppComponent?
I have an HomeFragment which should implement the HomePresenter with "new HomePresenter(networkService)" but now I don't know how to use the DI defined
EDIT - FIX:
HomeFragment.java:
public class HomeFragment extends Fragment {
private static final String TAG = "FW.HomeFragment";
#Inject
HomePresenterImpl mHomePresenter;
public static HomeFragment newInstance() {
return new HomeFragment();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FwApplication.component(getActivity()).inject(this);
}
Then I modified the presenter constructor in this way:
#Inject
public HomePresenterImpl(NetworkService networkService) {
mNetworkService = networkService;
mInteractor = new InteractorImpl(mNetworkService);
}
Then NetworkService is injected automatically.
I was wondering if it is correct in this way since I have to call for every fragment I have that needs a presenter constructed in the same way as the one above the following code:
FwApplication.component(getActivity()).inject(this);
You are mixing thing up. To provide your presenter, you should switch to something like the following:
Use constructor injection if possible. It will make things much easier
public class HomePresenterImpl {
#Inject
public HomePresenterImpl(NetworkService networkService) {
// ...
}
}
To provide the interface use this constructor injection and depend on the implementation:
Presenter<FwView> provideHomePresenter(HomePresenterImpl homePresenter) {
return homePresenter;
}
This way you don't have to call any constructors yourself. And to actually inject the presenter...
public class MyFragment extends Fragment {
#Inject
Presenter<FwView> mHomePresenter;
public void onCreate(Bundle xxx) {
// simplified. Add your modules / Singleton component
PresenterComponent component = DaggerPresenterComponent.create().inject(this);
}
}
This way you will inject the things. Please read this carefully and try to understand it. This will fix your major problems, you still can not provide 2 presenters of the same type from the same module (in the same scope)
// DON'T
#Provides
Presenter<FwView> provideHomePresenter(NetworkService networkService) { /**/ }
#Provides
Presenter<FwView> provideSearchPresenter(NetworkService networkService) { /**/ }
This will not work. You can not provide 2 objects of the same kind. They are indistinguishable. Have a look at #Qualifiers like #Named if you are sure this is the way you want to go.
You do not have to provide Presenter if #Inject annotation is used in the constructor. #Inject annotation used in the constructor of the class makes that class a part of dependencies graph. So, it also can be injected when needed.
On the other hand, if you add #Inject annotation to fields, but not to constructors, you have to provide that class.
i'm trying to use Dagger 2 into my apps but i'm having some problems regarding the entities repository and i haven't figured it out what i'm missing.
Here is my Application Component:
#Singleton
#Component(
modules = {
AndroidModule.class,
RepositoryModule.class
}
)
public interface ApplicationComponent {
void inject(AndroidApplication app);
IDependenceyRepository dependencyRepository();
}
My modules:
#Module
public class RepositoryModule {
#Provides #Singleton IDependenceyRepository provideDependendencyRepository(Context context) {
return new DependencyRepository(context);
}
}
#Module
public class AndroidModule {
private final AndroidApplication app;
public AndroidModule(AndroidApplication app) {
this.app = app;
}
#Provides #Singleton Context provideApplicationContext() {
return app;
}
}
My AndroidApplication:
public class AndroidApplication extends Application {
private ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
setupGraph();
}
private void setupGraph() {
component = DaggerApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.repositoryModule(new RepositoryModule())
.build();
component.inject(this);
}
public ApplicationComponent component() {
return component;
}
}
And here is my activity:
public class MainActivity extends Activity {
#Inject
IDependenceyRepository repository;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.testRepository();
}
private void testRepository() {
Dependency dep = new Dependency();
dep.setDescription("XXX");
dep.setName("AAAA");
try {
repository.save(dep);
Log.d("", repository.queryAll().get(0).getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
What happens is that i'm getting a null pointer exception into the repository. He is not being injected.
If i add this line though:
repository = ((AndroidApplication) getApplication()).component().dependencyRepository();
It works, but the point of the DI it's not to have to worry about this, or am im wrong about that?
I've tried some other example and tutorials but haven't managed to solve my problem.
Thanks in advance,
david.mihola's comment is correct: in order to be able to have #Inject fields initialized, you need to have a method (typically void inject(MyClass)) in your component.
It's worth noting (not specifically for your question, but it could come up) that if you have a base class:
public class BaseActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((AndroidApplication) getApplication()).component().inject(this);
}
}
and in your component:
void inject(BaseActivity);
Injecting into a subclass that has it's own #Inject fields won't work:
public class ActualActivity extends BaseActivity {
#Inject IDependenceyRepository repo;
}
This is because Dagger needs to know the concrete classes that will be injected at compile time, not runtime (like Dagger 1 could do).