My goal is to write instrumentation tests where the class under test is an Activity which has a mock presenter injected into it. I'd also like the presenter to have the same life as the activity that holds it. Finally, the mocks presenter that gets injected in the activity should also be able to be referenced from the test class so that the mock can be setup, verified etc... as part of the test.
Project setup:
I'm using gradle flavors to break the project into source sets that can be used for the different types of tests I'm trying to write. I have a mockRepo flavor where I have Dagger components and modules that create a "real" instance of all of my dependencies except for my repositories which I mock. I have an endToEnd flavor where I inject real instances of every dependency. The last flavor I'm attempting to add is a mockPresenter flavor which creates mock instances of the presenters so that I can truly isolate my views and ensure that when an activity's lifecycle methods run that the correct methods are called on the presenter.
The mockRepo and endToEnd flavors were pretty straightforward to write because in both cases I was able to extend the application class for testing and was able to swap out different dagger components in the application class which made it very easy to inject a mock repository.
The mockPresenter flavor is giving me problems. The issue is that I'm unable to inject a mock presenter into MainActivity without adding a method like
DaggerMainComponent getMainComponent() {
return mDaggerMainComponent
}
to my application class. I'd prefer not to do that because it seems like the application class shouldn't need to know about DaggerMainComponent at all and it's just a workaround.
Here's how the injection code for MainActivity currently looks:
#Inject
MainContract.Presenter mMainPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMainComponent = DaggerMainComponent
.builder()
.appInjector(((MyApplication) getApplicationContext()).getAppComponent())
.mainModule(new MainModule())
.mainPresenterModule(new MainPresenterModule())
.build();
mMainComponent.inject(this);
mMainPresenter.setView(this);
}
What I like about doing things this way is that in onCreate I used the Dagger generated class called DaggerMainComponent to create an instance of component which guarantees that I will get a new Component which guarantees that I will get a new presenter every time onCreate runs. Also, I can get a reference to the component in onCreate and dereference it in onDestroy(). However, it also seems that it isn't possible to inject a mock MainPresenter because the only chance I have to do that is this here:
.mainPresenterModule(new MainPresenterModule())
MainPresenterModule only creates "real" instances of the presenter and if I were so use something like:
.mainPresenterModule(new MockPresenterModule())
then there would be no way to use a "real" version of the presenter for the production version of the app.
The workarounds that I've seen for this situation all seem to involve having a method on the application class that either return a component or a module. In the production version of the app the application class would return either a component that depends on modules that provide real instances or could even just supply the module itself. The test version of the app would have a test application that would extend the production application class and override some of the methods so when the test application was running it would return either the mock version of the component which would declare modules that would supply mocks or it would return the mock modules directly.
The other workaround I've seen uses an #ExposedForTesting annotation in the application class which seems like something I'd like to avoid.
Having the application class expose getter methods so that mock / real dependencies can be injected seems like a code smell to me because I can't see a real reason that the application class should know about the dependencies that should be injected into MainActivity. Also, it seems like you'd end up with one getter method per Activity or Fragment which would start to bloat the application class.
Is there a way to inject mocks into an Activity so that the application class doesn't play such a big part in providing the correct modules or components?
Related
How to declare activities and fragments for non-base feature modules?
As we had only a base feature module and a single feature module, we could declare the App class in the feature module and had our graph be loaded there. This meant being able to use ContributesAndroidInjector and the standard dagger approach for android.
Now, adding more feature modules, we can't do the same. The application class must stay in the base feature module, which means it can't declare, for example, activities belonging to features.
My thoughts:
Since AndroidInjection.inject(this) will look for the activity injector in the app class, we can't use it. In other words, no DaggerAppCompatActivity.
So the idea is that activities belonging to feature modules should, instead, create their own component and inject themselves.
Still, it should be possible for fragments to use the #ContributesAndroidInjector thing. Right? The AndroidInjection class will get the injector from the parent activity, so if we fix our activities, they will expose the correct injector so that fragment code can be left as is.
For this reason, the feature activities must implement HasFragmentInjector and have a #Inject annotated DispatchingAndroidInjector for fragments.
But two things don't work here.
Feature fragments and activities still need some #Singleton annotated objects from the base feature component graph, so our SpecialFeatureComponent must somehow be linked to the BaseFeatureComponent.
It seems that the only way of doing so is by using the dependencies parameter:
#Component(
dependencies = [BaseFeatureComponent::class],
modules = [SpecialFeatureModule::class] // #contributes fragment
)
interface SpecialFeatureComponent
The SpecialActivity creates this component, passes the BaseFeatureComponent to its builder, and injects itself.
However, compilation fails due to MissingBinding errors. Some of the objects in the special feature module need #Provide, #Singleton annotated objects from the base feature component, and dagger doesn't seem to find them properly. (these objects are not in BaseFeatureComponent, but rather in its attached modules)
How to fix this?
I have read that exposing them directly in BaseFeatureComponent, rather than in its dependency modules, should fix the issue, but it's not something we would like to do, as there are lots of them and it would be yet another list to be maintained.
As is, the unscoped SpecialFeatureComponent depends on the #Singleton scoped BaseFeatureComponent. This is not possible, so we have to add a ActivityScope annotation.
#ActivityScope
#Component(
dependencies = [BaseFeatureComponent::class],
modules = [SpecialFeatureModule::class] // #contributes fragment
)
interface SpecialFeatureComponent
Now we are told that the subcomponents generated by #ContributesAndroidInjector for fragments, unscoped, may not reference scoped bindings. So we add a #FragmentScope annotation there.
#Module
abstract class SpecialFeatureModule {
#FragmentScope
#ContributesAndroidInjector
internal abstract fun specialFragment(): SpecialFragment
}
Now we are told that these subcomponents may not reference bindings with a different scope! Which are #Singleton objects provided by the base feature graph.
How to fix this?
So here are the things I know from the doc
Dagger Android under the hood is creating subcomponent for each Activity annotated with ContributesAndroidInjector
You can apply custom scope to the method where ContributesAndroidInjector is annotated to
If two sibling subcomponents have the same scope, they will still have different scope instances
If an Activity is in a subcomponent, it can have its own subcomponent which can contain Fragments. Those Fragments will share the scoped instances the Activity has.
Now my question is:
How to have one Activity be a subcomponent of another activity using Dagger Android?
I want to do this because I want to achieve things like #UserScope/#SessionScope.
From this I know that I can do it with just Dagger not Dagger Android. But with Dagger Android, you can only have the Application (which is the AndroidInjector) to inject Activity. You can not have an Activity used as a holder or host of the parent subcomponent to inject another Activity.
Am I understanding it correctly?
05/14/2018 Update:
I ended up getting rid of Dagger Android. So no more ContributesAndroidInjector, just pure Dagger. And to inject Activity/Fragment, I use the way that's recommended here. It will be something like this:
class MyActivity : AppCompatActivity() {
private val factory: ViewModelProvider.Factory = Injector.myCustomScope().factory()
}
And we are trying to make sure the factory is the only thing that Activity/Fragment needs.
So far it's been great.
How to have one Activity be a subcomponent of another activity using Dagger Android?
tl;dr You can't. Dagger Android follows a strict AppComponent > ActivityComponent > FragmentComponent scheme and there is no way to add custom scopes in-between.
I suggest you have a look at the Dagger Android source code, it's really not that much. It's basicalle a HashMap for each layer where you look up the component builder and build the subcomponent. A fragment looks at its parent Activity, an Activity looks at the Application. There is no feature where you can add custom components between layers.
What you can do is create your own variant of "Dagger Android" where you can implement your own interfaces and mix/match components as you need them. But that's quite a bit of extra work. I created a #PerScreen scope that survives configuration changes as a proof of concept if you are interested to see how you could do such a thing.
You can create a custom Scope called for example #PerScreen, also you will have #PerActvity scope. The difference between these scopes is that the #PerActivity scope will maintain shared dependencies between all activities like Context, Layout Inflater, etc. And all activity specific dependencies will be scoped as #PerScreen.
#PerApplication -> #PerActivity -> #PerScreen
This could structured like that.
I have explained scopes under the hood in my blog post, you can refer to it to get better understanding of this matter.
Is there a way in Dagger2 or in Dagger2 Android Injection support to inject the member instances without specifying the class names of the fragments.
I have a modular project where
The following line is asking to provide a binder Factory for the injectable class.
#Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
But my intention is to provide the injecting members through different modules in the project, where the I wouldn't need to specify the class name of the Fragment at all.
Is this possible in Dagger2 injection or not?
Is there a way in Dagger2 or in Dagger2 Android Injection support to inject the member instances without specifying the class names of the fragments.
tl;dr No. You always need a component that contains a .inject(FragmentInQuestion fragment) method for every Fragment you want to inject.
Dagger uses annotation processing at compile time to resolve all of your .inject(SomeFragment fragment) methods where it looks up fields annotated with #Inject and creates code to inject them.
It needs the actual class name, since it will only generate code for the class used with its fields.
class Fragment {
// no #Inject annotated fields, part of framework!
}
class SomeFragment extens Fragment {
#Inject SomeDependency dep;
}
class OtherFragment extens Fragment {
#Inject OtherDependency dep;
}
Declaring one generic component that injects all your fragments is impossible.
.inject(Fragment fragment) would not inject any properties, since Fragment does not contain any #Inject annotated fields. So neither of the dep fields in the above sample would be provided and they both would be null.
You could create a BaseFragment that contains common objects and write an injection for it, but again, any annotated fields of its children would not be supplied.
You could try some other workarounds, but in the end it would always mean that you'd be injecting or be working with a base type. While this might be feasible in some situations, I don't think it would work on more than some special edge cases.
The Android Injection part of Dagger 2 takes this approach and creates a generic interface that your components have to implement along with a Builder to bind the type in question.
interface AndroidInjector<T> {
inject(T instance)
}
By implementing this interface AndroidInjection can then look up (and create) the correct component and inject your Fragment. For the reasons mentioned above this will always be the actual class of your Fragment, and not some base type.
So by using the Android Injection part you won't be able to use some common base class, and even without it you'd have a hard time.
If you're looking for a way to move out the call of AndroidInjection.inject(this) to somewhere else you should look at the Google Android Architecture Components sample project where they use FragmentLifecycleCallbacks to inject the fragments globally at the right time.
My Goal :
To understand how scope works and how to Implement a UserScope that I can use over multiple Activities and reset/create a new one as required.
Methods I am using :
This Blog: http://frogermcs.github.io/building-userscope-with-dagger2/
It apparently explains the same thing that i am trying to achieve here.
Official Docs
http://frogermcs.github.io/building-userscope-with-dagger2/
Quick brief on Blog
Obviously, There is UserModule and UserComponent. Author has wrapped the creation of UserComponent under UserManager which has ApplicationScope. So UserManager is available at time of log in. when login is successful UserComponent is initialized via UserManager. Simple logic.
Now this already initialized #UserScope is used in couple of Activities, as you can see in the picture.
What I am struggling to understand
Take a look at UserComponent.
public interface UserComponent {
#Subcomponent.Builder
interface Builder {
Builder sessionModule(UserModule userModule);
UserComponent build();
}
UserDetailsActivityComponent plus(UserDetailsActivityComponent.UserDetailsActivityModule module);
RepositoriesListActivityComponent plus(RepositoriesListActivityComponent.RepositoriesListActivityModule module);
LogoutManager logoutManager();
}
Specifically UserDetailsActivityComponent and RepositoriesListActivityComponent are created through UserComponent. Like this,
#Override
protected void onUserComponentSetup(UserComponent userComponent) {
userComponent.plus(new UserDetailsActivityComponent.UserDetailsActivityModule(this)).inject(this);
}
So they first get pre-created in UserComponent through UserManager and then it calls onUserComponentSetup which then creates the appropriate Component and injects the current Activity.
I fail to comprehend with this pattern mentioned above, as I have read in the docs that we use plus(InjectionToBeDoneOn i) when we need the injection on a particular instance of InjectionToBeDoneOn. But why inject this Activity via this Component? What does this accomplish? Wouldn't it make sense to do this the conventional way in onCreate() of the activity with DaggerXYZComponent().Builder().Build().inject(activity)?
Also, I am missing decent material of how UserScope is implemented in Android which has life span from log-in to log-out but not bigger than the #Singleton scope.
we use plus(InjectionToBeDoneOn i) when we need the injection on particular instance of InjectionToBeDoneOn
Not quite. A component has basically 3 kinds of methods
SomeDependency provideDependency() which just creates / provides some dependency to subcomponents, or for manual retrieval (basically a getter)
void inject(MyAndroidFrameworkClass object) that injects an object with its dependencies
SomeSubComponent plus(SubComponentModule module) that creates a subcomponent, adding additional modules
You're mixing up 2. and 3. here.
// user scoped component
userComponent
// create a subcomponent (UserDetailsActivityComponent)
.plus(new UserDetailsActivityComponent.UserDetailsActivityModule(this))
// use the UserDetailsActivityComponent that was just created and inject with it
.inject(this);
UserDetailsActivityComponent is a subcomponent of UserComponent, which is why the userComponent gets extended .plus(somemodule) to create a subcomponent. If your submcomponent does not need additional modules you can also just use .plus() because to Dagger the important thing is the return type or signature in general.
If it returns another component, then it creates a SubComponent.
If it hast one parameter and returns void or the parameters type, then it is an inject method
If it has no parameters and returns some type is is a provides method (1.) to expose some dependency
but why inject this Activity via this Component? What does this accomplish?
If you were to create UserDetailsActivityComponent from scratch, it would only see and know about what it can provide itself. If you have some #Singleton somewhere it could not access any of it, because it is not part of the object graph.
A subcomponent extends another component, adding to the object graph. If you have a #Singleton A and your UserComponentn needs A to provide B, with a subcomponent this will work, without it you will get a cannot be provided error.
Dagger is no magic. It really just builds up a directed graph and checks whether everything is fine. It will complain if some dependencies have cyclic dependencies on one another or if some part of the graph doesn't have access to dependencies it need.
Your UserComponent holds your userdata. For simplicity lets say it holds the UserName. Now UserDetailsActivity might want to display UserName, but it needs some way to get it.
By using the #Singleton AppComponent as a parent you'd have access to some Apis, but not the user scoped ones. You could move the user scoped objects into the #Singleton AppComponent, but then you'd either have to recreate the AppComponent every time the user changes (which kind of defeats the purpose of declaring it #Singleton, or you'd have to find some other means to update / change the user.
If you want to do it the Dagger way, you create a UserComponent that adds the User to the graph. That way subcomponents can access it and do their user things.
When the user changes you have to make sure to destroy any activities / fragments that used the UserComponent, and you just recreate everything—with a new user.
wont it make sense to do in conventional way in OnCreate() of the activity with DaggerXYZComponent().Builder().Build().inject(activity)
You can do that of course. The author just put the calls to app.getAppcomponent().getUserManager().getUserComponent() or something like this into their BaseActivity and BaseUserActivity so that you wouldn't have to repeat the same lines of code every time. This method will basically still be called in onCreate, it just enables you to use the components directly, without fetching them every single time.
You can obviously remove those template methods and inline everything in onCreate, leading to duplicated code, making maintenance harder in the long run.
i am missing decent material of how UserScope is implemented in android which has life span from log-in to log-out but not bigger than #SingleTon scope.
Android doesn't help and it's your job to clean up after yourself. If the user changes you purge everything the UserComponent and its SubComponents touched, and recreate it with the new user.
You will have to store the UserComponent with the current user either in the Application class, some "real" singleton, or some "Manager" in the application component with a #Singleton scope. Every approach has their own benefits I guess.
Scopes just point out to Dagger that one object should exist within a Scope only once. It doesn't matter what you name it, or how many objects are in the Scope. Dagger only needs them to ensure that there are no dependency cycles.
Say class A depends on B, and that I want to test class A.
I create a test for class A in which I want to mock B.
Class B is injected into class A using Dagger2 (using a Module and a Component). Meaning, class A has a class member:
#Inject
B mB;
In my test class, I create an instance of A in the setUp() method.
How to I provide the mocked instance of the class B to A?
You will need to provide a mock implementation using a mock build flavor if you normally inject your dependency with Dagger. Usually this is done by replacing something like ProdModule with MockModule and then #Provides a mock implementation instead in your mock or test flavor.
Otherwise, a good testing option is Mockito if you want to guarantee that your mock returns what you want it to so that you don't have to worry about the mock implementation having a bug in it.