I have a Presenter class which uses a field injected through Dagger, it looks something like this:
public class RssListPresenter {
#Inject
RssService rssService; // <-- injected field
public RssListPresenter() {
setupDI();
}
private void setupDI() {
DaggerNetworkComponent.builder()
.networkModule(new NetworkModule())
.build()
.inject(this);
}
public void loadItems() {
Rss rss = rssService.getRssFeed()
// ....
}
}
Everything works fine. Now, I would like to unit test the RssListPresenter class. The question is how do I provide a mock RssService to the presenter?
Ofcourse I can add a new method setRssService(RssService rssService) to the presenter and use it to provide the mock from unit tests, but adding this method just for unit tests does not feel right. What would be the correct way to handle this?
For completeness here are the module and component declarations:
#Singleton
#Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(RssListPresenter presenter);
}
#Module
public class NetworkModule {
#Provides
Retrofit provideRetrofit() {
// ...
}
#Provides
#Singleton
RssService providePcWorldRssService(Retrofit retrofit) {
return retrofit.create(RssService.class);
}
}
Property injection is like that is not so easy to test. In this case, Constructor injection is much better. Refactor your constructor to look like this:
private final RssService rssService;
#Inject
public RssListPresenter(RssService rssService) {
this.rssService = rssService;
}
Now you can test it easily:
//mocks
RssService mockRssService;
//system under test
RssListPresenter rssListPresenter;
#Before
public void setup() {
mockRssService = Mockito.mock(RssService.class);
rssListPresenter = new RssListPresenter(mockRssService);
}
You probably shouldn't be using DaggerNetworkComponent.inject(this) inside RssListPresenter. Instead you should be configuring dagger so that when it injects members into your top-level classes (Activity, Fragment, Service) it can access the object graph and create an instance of your RssPresenter.
Why only put injectors in Activity and Service and not in something like RssListPresenter? These are classes that are instantiated by the Android system and so you have no choice but to use injectors.
To clarify, Activity, Fragment etc. are ideal injection targets. RssListPresenter etc. are injected dependencies. You need to configure the dependency injection framework, dagger, so that it can provide the correct dependencies to inject into the injection targets.
So you will also need to write a #Provides method for RssListPresenter
#Provides provideRssListPresenter(RssService rssService) {
return new RssListPresenteR(rssService);
}
Your class violates some of the S.O.L.I.D principles, and this makes it very difficult to unit test it. Your class for sure violats the SRP(Single Responsability Principle) as it has more than one reason to change. There is also a possible Dependency Inversion violation.
I advice to rethink the model of the classes, so that each of them performs a specific function, and has one reason to change.
Related
Colleagues, I welcome you all! Tell me how to decide, or how to act. (Java11, SpringBoot, testing - Spock Framework) I need to write a test that will test a class method, the whole problem is that the method of the class under test calls another service through inheritance, which is not declared in the class under test, but in its abstract ancestor. How to test such a story? If this service were declared in the class under test itself, then everything is clear, I would create a mock in the test and pass it to the constructor, but what if this service is located at the ancestor? I am attaching an example code below.
// The class to be tested
#Service
public class ServiceForTest extends AbstractComponent{
public String methodForTest (String s) {
return someService.generateString(s);
}
}
//An abstract class from which the tested one is inherited and which contains the service
public class AbstractComponent {
#Autowired
protected SomeService someService;
}
public interface SomeService {
String generateString(String s);
}
#Service
public class SomeServiceImpl implements SomeService{
#Override
public String generateString(String s) {
return s;
}
}
And below is an example of what I would do if the service was in the class being tested
//TestClass
#Service
public class ServiceForTest extends AbstractComponent{
final SomeService someService;
public ServiceForTest(SomeService someService) {
this.someService = someService;
}
public String methodForTest (String s) {
return someService.generateString(s);
}
}
class test groovy, Spock Framework
class ServiceForTestTest extends Specification {
ServiceForTest serviceForTest
void setup(){
SomeService someServiceMock = Mock(SomeService)
someServiceMock.generateString("TEST") >> "TEST"
serviceForTest = new ServiceForTest(someServiceMock)
}
def "Test for return current value"(){
when:
def methodForTest = serviceForTest.methodForTest("TEST")
then:
methodForTest == "TEST"
}
}
You use #Autowired, i.e. some kind of dependency injection framework such as Spring or Java EE CDI. Those frameworks have testing support. Specifically for Spring testing, Spock has a Spring module which you can use. I am not a Spring user, so I cannot tell you how to exactly do that, but the documentation is pretty good.
As a general answer, even without any framework support you can test this easily, if you follow the convention to put the test into the same package as the class under test. Because the field you want to inject a mock into is protected, it means for the JVM that all subclasses, but also other classes in the same package have access to it. I.e., you can simply set the value:
serviceForTest = new ServiceForTest()
serviceForTest.someService = someServiceMock
Or, more elegantly using a Groovy-style constructor which implicitly sets field values:
serviceForTest = new ServiceForTest(someService: someServiceMock)
Generally, I recommend constructor or setter injection rather than relying on field injection (especially when fields are private), because then with regard to testability you have a strict dependency to your DI framework and cannot easily write unit tests. So if you can refactor, I recommend you to do it. You just noticed that testing such things can be kind of a headache, unless you have a way out like in this particular case with the protected field. But that is not so super refactoring-friendly.
I'm getting started with Dagger 2 and trying to work out how to get #Inject and #Provides to work together well. It's easy to get everything working when the top-level class is injected. Like this:
class TopLevelClass {
#Inject
SecondaryClass1 class1;
private final SecondaryClass2 class2;
#Inject
TopLevelClass(SecondaryClass2 class2) {
this.class2 = class2;
}
}
#Module
class MyModule {
#Provides
SecondaryClass2 provideSecondaryClass2() {
return new SecondaryClass2();
}
}
However, everything stops working when I have to make a #Provides method for the top level class as well. Like this:
class TopLevelClass {
#Inject
SecondaryClass1 class1;
private final SecondaryClass2 class2;
TopLevelClass(SecondaryClass2 class2) {
this.class2 = class2;
}
}
#Module
class MyModule {
#Provides
TopLevelClass provideTopLevelClass(SecondaryClass2 class2) {
return new TopLevelClass(class2);
}
#Provides
SecondaryClass2 provideSecondaryClass2() {
return new SecondaryClass2();
}
}
With this example, I find that SecondaryClass1 never gets injected, so I get an NPE when I try to access it. Is there a different way that I'm supposed to do this for a provided top-level class?
P.S. I don't think it's relevant, but here's the component I'm using just in case it matters:
#Component(modules = {MyModule.class})
interface MyComponent {
TopLevelClass getTopLevelClass();
}
Thanks!
You have 2 options:
Make use of constructor injection and let Dagger create & inject the object (without any #Provides annotated method) The object will be ready to use without any need for code. This is where Dagger shines.
Create the object yourself and make sure it is correctly set up before returning it from the #Provides annotated method
I don't know why you would prefer the second option, or why you would even want to use field injection when you can make use of the constructor, but, as you found out, fields won't be injected if you manually construct the object. None of the generated Dagger code will run—you'll have to do this yourself.
One way that comes to mind is to request the component and then inject the object.
#Provides
TopLevelClass provideTopLevelClass(SecondaryClass2 class2, MyComponent component) {
TopLevelClass tlc = new TopLevelClass(class2);
component.inject(tlc);
return tlc;
}
I have an app with activities and fragments with dependencies injected via dagger 2
I am able to do field injection in activities and fragments but not able to do constructor injection in other classes.
Here's my relevant code
#Module
public abstract class MainFragmentProvider {
#ContributesAndroidInjector(modules = HomeFragmentModule.class)
abstract HomeFragment provideHomeFragmentFactory();
}
and
#Module
public class HomeFragmentModule {
...
#Provides
static HomePresenter provideHomePresenter(HomeView homeView, HomeInteractor homeInteractor) {
return new HomePresenter(homeView, homeInteractor);
}
How can I write code so that I can get dependencies directly in HomePresenter by constructor injection instead of writing provideMethods in module. I am doing this because every time I want to change the constructor arguments in this case, I need to change the module code as well.
How can I do something like this in HomePresenter's constructor?
#Inject
public HomePresenter(HomeView homeView, HomeInteractor homeInteractor) {
this.homeInteractor = homeInteractor;
this.homeView = homeView;
}
To inject constructor, Dagger has to know where to get parameters passed to it i.e.
you have to provide
HomeView homeView, HomeInteractor homeInteractor
So create also methods for providing other dependencies:
#Provides
static HomeView provideHomeView() {
return ...
}
#Provides
static HomeInteractor provideHomeInteractor() {
return ...
}
I don't know much about the android extensions for dagger 2 but as far as I know there are two ways to achieve the result you are looking for.
In the relevant component you can specify a method with your type:
interface SomeComponent {
HomePresenter presenter(); // Method name does not matter here, only the type
}
and access it like this
class Home {
HomePresenter presenter;
void initialize() { //This could be your onCreate or wherever you typically inject
presenter = getSomeComponent().presenter();
}
}
or you can request it if you specify an inject method for your Home object:
interface SomeComponent {
void inject(Home home);
}
class Home {
#Inject HomePresenter presenter;
void initialize(){
getSomeComponent().inject(this);
}
}
In both cases you must ensure your Component includes the appropriate Modules.
My class depends on some services which needs to take few parameters and then make network call, currently I am passing those parameters and then creating those services via a factory injected into my class. I need to inject those services as a dependency instead, I know that I can create providers for them but in most of the examples I see that the providers are often bound to the fixed values like serveraddres etc. but I need to give then values during run time.
Below is my example code:
public SomeClass {
private final SomeFactory someFactory;
#Inject
SomeClass(SomeFactory factory) {
someFactory = factory;
}
public Foo getFoo(String fooId) {
FooService fooService = someFactory.getFooService(fooId);
return fooService.getFoo();
}
}
What I need to do is:
public SomeClass {
private final FooService fooService;
#Inject
SomeClass(FooService fooService) {
this.fooService = fooService;
}
public Foo getFoo(String fooId) {
return fooService.getFoo();
}
}
Update 1
Making the use case more clear:
#Provides
#RequestScoped
public SomeService provideSomeService(Dep1 dep1, String code) throws IOException {
return new SomeService.Builder()
.withApplicationName("Foo")
.setCode(code)
.build();
}
Here, code can be null by default and when needed I can give some value in it.
Can I somehow pass arguments to the provider before its created?
If you have a binding for your value (here, code is a String without a binding annotation), then your Update 1 is exactly what the code would look like.
In practice, there are a few differences:
Constants like int and String values are generally annotated with a binding annotation, either #Named or a custom annotation.
If you need to inject a value into an object graph after Guice initialization, but have a deep enough object graph that dependency injection is still a good idea, you can create a child injector. This way you can make a #Named("code") String accessible within one action or object, but not across your entire Guice application.
If your value for code is dynamic enough that it can't be provided through Guice as a key of its own, then you'll have to pass it in using a factory of some sort. For a Builder-based object, I'd say that your SomeFactory implementation is the best that I would come up with in your case.
If you don't need to use a Builder, and can let Guice create the object based on your fields or constructor parameters, you can code-generate a Factory.
Guice can generate a factory for you through FactoryModuleBuilder, in a feature known as "assisted injection".
Google's other tool, AutoFactory, will code-generate a factory implementation that works in both Guice and Dagger. (It's bundled as "Auto", which includes a model object generator called AutoValue that also generates annotation implementations.)
I put a small demonstration of a child injector and assisted injection in my other SO answer here.
The best approach here is to parameterize the module and pass the parameter through to a provider that you create at runtime:
public class MyModule extends AbstractModule {
private final String code;
public MyModule(String code) {
this.code = code;
}
#Override public void configure() {
Provider<Dep1> depProvider = getProvider(Dep1.class);
bind(SomeService.class)
.toProvider(() -> new SomeService.Builder()
.withApplicationName("Foo")
.withDep(depProvider.get())
.setCode(code)
.build())
.in(RequestScoped.class);
}
}
I am currently trying to integrate Dagger 2 into an Android application. My project setup is as follows:
library
app (depends on library)
In my library project I defined a class that I'll later inject into other classes that need it (Activities and regular classes) in the library as well as the app project.
#Singleton
public class MyManager{
#Inject
public MyManager(){
//Do some initializing
}
}
Now - for instance in my Fragments or Activities or regular classes I'd inject the above Singleton as follows:
public class SomeClass{
#Inject
MyManager myManager;
}
Or so I thought, because in practice myManager is always null. And apparently it's constructor is never called either, so I guess I must be missing something configuration-wise? Or maybe I misunderstood the documentation and it's not meant to work this way at all? The purpose of MyManager class is to be an application-wide accessible component-accumulating entity - that's why I went for the #Singleton.
UPDATE
To avoid confusion: I mentioned my having components somewhere in a comment I think - this refers to components in the sense of "component based design" and has nothing to do with dagger. The dagger-based code I have is all listed above - there is nothing else related to dagger in my code.
When I started adding #Component I had some compiler issues, because my dagger2 was not setup properly - check out this really helpful thread on how to setup dagger2 correctly: https://stackoverflow.com/a/29943394/1041533
UPDATE 2
Here is my updated code, based on G. Lombard's suggestions - I changed the code as follows - the original Singleton is in the library project:
#Singleton
public class MyManager{
#Inject
public MyManager(){
//Do some initializing
}
}
Also in the library project is the bootstrap class:
#Singleton
#Component
public interface Bootstrap {
void initialize(Activity activity);
}
Then I use the above Bootstrap class in my activity (in my concrete app, NOT in the library project! I do however also have Classes/Activities in the library that'll access Bootstrap to inject MyManager):
public class MyActivity extends Activity{
#Inject
MyManager manager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DONT DO THIS !!! AS EXPLAINED BY EpicPandaForce
DaggerBootstrap.create().initialize(this);
}
}
But even after this line:
DaggerBootstrap.create().initialize(this);
the manager instance is still null, i.e. not injected.
I just found this: https://stackoverflow.com/a/29326023/1041533
Which if I don't misread, implies I need to specify every single class in the Bootstrap class that will use #Inject to have stuff injected. Sadly - this is not an option, as I have more than 40 classes and activities for which I'd have to do that.
Meaning my Bootstrap interface apparently would have to look something like this:
#Singleton
#Component
public interface Bootstrap {
void initialize(ActivityA activity);
void initialize(ActivityB activity);
void initialize(ActivityC activity);
void initialize(ActivityD activity);
void initialize(ActivityE activity);
void initialize(ActivityF activity);
//and so on and so forth...
}
If the above is true, that would not be worth it for my use case. Plus: Seems there is no compile-time check, if I forgot to specify one of my 40+ classes here? It just wont work - i.e. crash the app at runtime.
You're making a mistake in that you are using
DaggerBootstrap.create().initialize(this);
in your Activity, as scopes are not shared across multiple component instances. What I recommend is using a custom application class
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Bootstrap.INSTANCE.setup();
}
}
#Component
#Singleton
public interface _Bootstrap {
void initialize(ActivityA activityA);
//void initiali...
}
public enum Bootstrap {
INSTANCE;
private _Bootstrap bootstrap;
void setup() {
bootstrap = Dagger_Bootstrap.create();
}
public _Bootstrap getBootstrap() {
return bootstrap;
}
}
Then you could call it as
Bootstrap.INSTANCE.getBootstrap().initialize(this);
This way, you share the component across your classes. I personally named Bootstrap as injector, and _Bootstrap as ApplicationComponent, so it looks like this:
Injector.INSTANCE.getApplicationComponent().inject(this);
But that's just my typical setup. Names don't really matter.
EDIT: To your last question, you can solve this by subscoping and component dependencies.
Your library project should be able to see only the library classes, correct? In that case, all you do is
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface LibraryScope {
}
#Component(modules={LibraryModule.class})
#LibraryScope
public interface LibraryComponent {
LibraryClass libraryClass(); //provision method for `MyManager`
}
#Module
public class LibraryModule {
#LibraryScope
#Provides
public LibraryClass libraryClass() { //in your example, LibraryClass is `MyManager`
return new LibraryClass(); //this is instantiation of `MyManager`
}
}
public enum LibraryBootstrap {
INSTANCE;
private LibraryComponent libraryComponent;
static {
INSTANCE.libraryComponent = DaggerLibraryComponent.create();
}
public LibraryComponent getLibraryComponent() {
return libraryComponent;
}
}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ApplicationScope {
}
#Component(dependencies={LibraryComponent.class}, modules={AdditionalAppModule.class})
#ApplicationScope
public interface ApplicationComponent extends LibraryComponent {
AdditionalAppClass additionalAppClass();
void inject(InjectableAppClass1 injectableAppClass1);
void inject(InjectableAppClass2 injectableAppClass2);
void inject(InjectableAppClass3 injectableAppClass3);
}
#Module
public class AdditionalAppModule {
#ApplicationScope
#Provides
public AdditionalAppClass additionalAppClass() { //something your app shares as a dependency, and not the library
return new AdditionalAppClass();
}
}
public enum ApplicationBootstrap {
INSTANCE;
private ApplicationComponent applicationComponent;
void setup() {
this.applicationComponent = DaggerApplicationComponent.builder()
.libraryComponent(LibraryBootstrap.INSTANCE.getLibraryComponent())
.build();
}
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
}
Then
#Inject
LibraryClass libraryClass; //MyManager myManager;
...
ApplicationBootstrap.INSTANCE.getApplicationComponent().inject(this);
It's hard to say what your problem was, since you didn't show what your Component looks like and whether you have multiple components etc.
Assuming this logical structure:
/app
MainComponent
SomeClass // where MyManager is to be injected
MainActivity // where SomeClass is to be injected
/library
LibraryComponent
MyManager // Singleton
Then your classes as listed would inject correctly with the following configuration:
#Singleton
#Component
public interface LibraryComponent {
MyManager getMyManager();
}
and the app-level component to inject dependencies into the activity:
#ActivityScope
#Component(dependencies = LibraryComponent.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
Note that MainComponent depends on LibraryComponent, but because the latter has singleton scope, you need to define a scope for the other one too, which I was I used the "activity scope" here. (Or you could also just make the MainComponent a singleton and get rid of the LibraryComponent completely if that suits your needs.)
Finally it's all injected into the activity like this:
#Inject
SomeClass someClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
.libraryComponent(DaggerLibraryComponent.create())
.build()
.inject(this);
someClass.doSomething();
}
I've put a working sample here on GitHub
Update 1:
If I understand your setup correctly, you've so far only used the #Singleton and #Inject annotations on the two classes listed (MyManager and SomeClass), and there is no other Dagger-related code in your project.
In that case, the reason your MyManager isn't getting injected, is because Dagger doesn't know how to provide/instantiate the dependencies. This is where the "components" come in that I mentioned above. Without any Dagger 2 components (interface or abstract class annotated with #Component), your dependencies won't get injected automatically.
I don't know if you have experience with Dependency Injection concepts, but assuming you don't, I'll step through the minimum basics you'll need to understand to get your MyManager injected into SomeClass:
First: when you use DI, you need to understand the difference between "newables" and "injectables". This blogpost by Misko Hevery explains the details.
This means, you can't new up your SomeClass. This won't work:
mSomeClass = new SomeClass();
Because if you did that (say in an activity or fragment), Dagger will have no idea that you expected a dependency to get injected into SomeClass and it has no opportunity to inject anything.
In order for its dependencies to get injected, you have to instantiate (or inject) SomeClass itself through Dagger too.
In other words, say in your Activity where SomeClass is used, you'll need:
#Inject
SomeClass mSomeClass;
Next, you need a Dagger component to perform the actual injection. To create a component, you create an interface with a method that takes your root object (say MainActivity) as argument, e.g.:
#Singleton
#Component
public interface Bootstrap {
void initialize(MainActivity activity);
}
Now when you build your project, Dagger 2 generates a class called DaggerBootstrap that implements this interface. You use this generated class to perform the injection, say in your activity's onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerBootstrap.create().initialize(this);
mSomeClass.doSomething();
}
I believe this generated component is the key part you're missing. Full code for above here.
Some useful Dagger 2 resources:
the official Dagger 2 guide
reddit page with lots of links
Update 2:
Seems like the final missing piece of the puzzle was that your component provided an inject method for the Activity base class, but not for your actual concrete activity.
Unfortunately Dagger 2 requires an inject method for each activity or other class you want to inject into.
As you mentioned, this will be annoying when you have many different activities in your app. There some possible workarounds for this, search for "dagger 2 inject base class", for example this suggestion by #EpicPandaForce: Dagger 2 base class injections
Also note, as pointed out by #EpicPandaForce in the comments, that in my simplistic example I called DaggerLibraryComponent.create() every time which is probably not what you want, since that component is supposed to provide your singletons, so you're probably better off getting the existing instance from somewhere else such as from your Application instance.