Field injection in unit tests with Dagger 2 - android

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

Related

Mocking dependency not listed in module

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

Dagger 2 doesn't provide my class as singleton, why?

I have a class:
public class MyFactory {
public MyFactory() {
}
}
My Dagger module:
#Module
public class MyModule {
#Provides
#Singleton
public MyFactory provideMyFactory() {
return new MyFactory();
}
}
My component:
#Singleton
#MyScope
#Component(modules = MyModule.class)
public interface MyComponent {
MyFactory getMyFactory();
}
My Scope:
#Scope
#Retention(RetentionPolicy.CLASS)
public #interface MyScope {
}
I expect MyFactory instance being provided by Dagger2 is a singleton however, it is not. I tried to call following code in two places of my project:
MyFactory factory = DaggerMyComponent.builder()
.build()
.getMyFactory();
Logcat.d("tag", "factory = " + factory);
My log shows me :
factory = :com.my.app.MyFactory#eaa9330
factory = :com.my.app.MyFactory#8bd552e
As you can see, they are two instances. So, My questions are:
Question 1. In above case, why Dagger doesn't provide MyFactory as a singleton??
Question 2. With my above code, does everytime calling DaggerMyComponent.builder().build(); create a new instance of MyComponent?
Question 3. Dagger also allow developer's class instance to be injectable by place a #Inject annotation in constructor without bothering having the module:
public class MyFactory {
#Inject
public MyFactory() {
}
}
developer can use it just by #Inject MyFactory factory;
In this case, how to make MyFactory singleton with Dagger 2?
Scopes are per component. If you create multiple components then you create multiple objects. #Singleton is no exception.
// same component
DaggerMyComponent firstComponent = DaggerMyComponent.builder().build();
firstComponent.getMyFactory() == firstComponent.getMyFactory(); // true
// second component
DaggerMyComponent secondComponent = DaggerMyComponent.builder().build();
firstComponent.getMyFactory() == secondComponent.getMyFactory(); // false
You will have to keep the component instances and reuse them, or you will always create new references. E.g. A common way is to place the #Singleton AppComponent within your Application and grab it from there when needed.
In this case, how to make MyFactory singleton with Dagger 2?
With constructor injection you just add the scope to the top of the class.
#Singleton // place scope _on_ the class
public class MyFactory {
#Inject
public MyFactory() {
}
}

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.

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 - nested injections, is it necessary to call inject()?

I'm new to Dagger and at the begininig I face some issues. I have simple structure so far in my project. My injection module:
#Module(
injects = {GameBoardFragment.class, GameManager.class},
complete = false,
library = true
)
public class GameObjectsProviderModule {
private final Application mApplication;
public GameObjectsProviderModule(Application application){
this.mApplication = application;
}
#Provides
#Singleton
public GameManager provideGameManager(){
return new GameManager();
}
#Provides
public Board getBoard(){
return new Board();
}
#Provides #Singleton #ForApplication Context provideAppContext() {
return mApplication;
}
My simplified custom app class looks like that:
public class MyApp extends Application {
private static ObjectGraph mApplicationGraph;
#Override public void onCreate() {
super.onCreate();
mApplicationGraph = ObjectGraph.create(new GameObjectsProviderModule(this));
}
public static ObjectGraph getObjectGraph(){
return mApplicationGraph;
}
}
And now, my fragment looks like that:
public class GameBoardFragment extends Fragment {
#Inject
GameManager mGameManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
MyApp.getObjectGraph().inject(this);
View root = inflater.inflate(R.layout.fragment_game_board, container, false);
findViews(root);
confViews();
return root;
}
}
And finally my GameManager class
public class GameManager {
#Inject Board mBoard;
public GameManager(){
MyApp.getObjectGraph().inject(this);
}
}
Andy hey, it works! Great. But my question is why it doesn't work in case I comment out this line:
MyApp.getObjectGraph().inject(this);
Do we have always explicitly call inject() function to make all necessary injections take place event in nested objects?
It looks not, as shows coffe maker example:
https://github.com/square/dagger/tree/master/examples/simple/src/main/java/coffee
Why then I have to call inject() in GameManager class to get it working?
Edit:
The consturctor injection approach works just fine.
But for future use I tried to get field injection running, and so far I haven't succeed.
I commented out both #Provide methods from module and I made my GameManager look like this:
#Singleton
public class GameManager {
#Inject Board mBoard;
#Inject
public GameManager(){
}
}
and Board:
public class Board {
#Inject
public Board() {
}
}
However mBoard doesn't get instantiated. I will try more and I suppose I figure out the proper solution.
You should rather use constructor injection (like for example the Thermosiphon does), and avoid field injection unless necessary. For example, let your GameManager have the Board as a constructor argument:
#Singleton
public class GameManager {
private final Board mBoard;
#Inject
public GameManager(final Board board){
mBoard = board;
}
}
Dagger will use this constructor to create an instance of the GameManager (hence the #Inject annotation), and notice it needs a Board instance. Using the ObjectGraph, it will create a Board first, and use that instance to create the GameManager. You can remove the #Provides GameManager method if you do it this way.
In your case, you have a #Provides Board method in your module. If you add an #Inject annotation to your Board constructor, you can remove this provides-method from your module:
public class Board {
#Inject
public Board() {
}
}
If you don't want to use constructor injection, the problem is that you told Dagger that you want to create your GameManager instance yourself (because you have the #Provides GameManager method). If you remove this method, and let Dagger create it for you like above but without the Board parameter in the constructor, Dagger will also notice the #Inject Board field and inject that as well.
A final remark. Remove the library = true and complete = false statements! These are not necessary at all in this example. Only add them if you really know what you're doing. By not having them, Dagger will create compile-time errors to notify you that something is wrong. If you do include them, you're telling Dagger "Hey, I know what I'm doing, don't worry, it's all correct", when in fact it isn't.
Edit
A quote from the Dagger1 site:
If your class has #Inject-annotated fields but no #Inject-annotated
constructor, Dagger will use a no-argument constructor if it exists.
Classes that lack #Inject annotations cannot be constructed by Dagger.
I do not use this method very often, so I could be wrong. I think this means that you should remove the #Inject annotation from your constructor, like so:
#Singleton
public class GameManager {
#Inject Board mBoard;
public GameManager(){ // Or remove the constructor entirely since it's empty
}
}
Since there is an #Inject annotation on the Board field, Dagger will know to use the no-argument constructor.
I was struggling with the same issue as most of the dagger examples everywhere use a Module with Provides and I had a hard time finding a complete example that just does not use Provides.
I created this one. It uses field injection (not constructor injection) and works just fine through the hierarchy without requiring any call to inject. I am using Dagger 1.2.2.
Main.java
import javax.inject.*;
import dagger.*;
import dagger.ObjectGraph;
public class Main {
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new CarModule());
Car car = objectGraph.get(Car.class);
car.start();
}
}
CarModule.Java
import dagger.Module;
#Module(injects = Car.class)
public class CarModule {
}
Car.Java
import javax.inject.*;
public class Car {
#Inject public Engine engine;
#Inject Car() {
System.out.println("Car constructor");
}
public void start() {
engine.start();
}
}
Engine.Java
import javax.inject.*;
public class Engine {
#Inject WaterPump waterPump;
Engine() {
System.out.println("Engine Constructor");
}
void start() {
waterPump.run();
System.out.println("starting engine.");
}
}
WaterPump.Java
import javax.inject.*;
public class WaterPump {
#Inject WaterPump() {
System.out.println("WaterPump Constructor.");
}
public void run() {
System.out.println("WaterPump running.");
}
}
The output is:
Car constructor
Engine Constructor
WaterPump Constructor.
WaterPump running.
starting engine.
Without The CarModule that declares it injects Car.Class this does not work. You get:
Exception in thread "main" java.lang.IllegalArgumentException: No
inject registered for members/Car. You must explicitly add it to the
'injects' option in one of your modules.
But notice that CarModule does not #Provides anything. It is dagger that automatically creates all the dependencies using the object graph.
Also note that you don't have to place an #Inject annotation on the default constructor if you have a #Inject field in the class. For Car class I used it on both the constructor and the field and in Engine class I used it just for the field and not for the constructor and it works fine as documented.

Categories

Resources