Closed chrisjenx closed 8 years ago
This is a big blocker for me, too. May you share your current workaround with us? I'm currently stuck replacing Picasso with a mock.
You can use Gradle's flavors and write different Module implementation in required flavor(s)
@artem-zinnatullin If you only want to override certain bindings, you still would have to copy the whole module with all other bindings, too. The other possibility would be to create a TestModule
, which however must be an empty class in all other flavors. Last but not least, what about non-Android environments where Dagger is used?
@artem-zinnatullin that is a work around, but not really using the build systems as intended. I feel there should be a more "natural" approach which plays nice with the build systems + dagger. I'm sure this has been discussed internally between the dagger team, just interested on thoughts/best practice and what direction this is going.
@svenjacobs @chrisjenx I understand, that this is not perfect solution.
In Dagger 1 I used module overriding + build flavors. I will work on unit tests + Dagger 2 this week and post my workaround.
@svenjacobs Gradle is not only for Android projects, we successfully use it for Java projects (backends and utils), Gradle supports many platforms, you can even build an iOS app with it.
@passsy hope you use Gradle, just create module class which provides mocked Picasso in test flavor
@artem-zinnatullin I know that Gradle is a generic build system, but flavors, the solution you suggested, is Android-specific and is coming from the Android Gradle plugin. Also Dagger might be used in a project with Maven or another build system.
@svenjacobs yes, you are correct, build flavors is Android Gradle plugin's feature
Build flavors are not the solution. I need to inject different mock classes for different tests.
This may be of some use, however it is likely broken at the moment due to some snapshot changes. https://github.com/bryanstern/dagger-instrumentation-example
@bryanstern As discussed above, using buildTypes, isn't really a "solution" more of a work around. But thanks for the example nonetheless.
+1 for this, it was possible with Dagger v1 but not anymore on v2.
This issue is important for us too. It somewhat looks like if tests where not part of the design of Dagger2, and I can't really believe that.
I currently use a work around : create a test module with a test scope, create an injector/component during test and inject the activity/entity under test with it. But it means that the activity for instance will receive twice the injection : one from production code and one for testing. It's not a very big deal but a bit awkward.
@RunWith(RobolectricTestRunner.class)
public class CarouselTest {
@Test
public void testCarouselWithDependency() {
ActivityController<Carousel> carouselActivityController = Robolectric.buildActivity(Carousel.class).create();
Carousel carousel = carouselActivityController.get();
di.DbTestAppComponent dbTestAppComponent = ((DbTestApplication) carousel.getApplicationContext()).getComponent();
dbTestAppComponent.inject(carousel);
carousel.init(null); //call anything that is done after the injection in onCreate
}
@TestScope
@Component(dependencies = {DbTestAppComponent.class}, modules = {TestDbTestAppModule.class})
public interface TestDbTestAppComponent {
void inject(Carousel carousel);
}
@Module(overrides = true) //that should be needed but is that implemented ?
public class TestDbTestAppModule {
@Provides
public SharedPreferences getSharedPreferences() {
return Robolectric.application.getSharedPreferences("a", 0);
}
}
}
@stephanenicolas I did something similar, but instead I created also a test component to mirror the production component, so the test classes only get injections from the test module, instead of getting twice. It sucks but works
@felipecsl Does your test component live in the androidTest
source tree? I'm trying to do the same but a Dagger_TestComponent
generated class (e.g. from the TestComponent
interface) isn't being created--I'm thinking that Dagger is not looking in my androidTest
source tree for components, but I'm not sure.
@twelve17 I'm using the new unit testing support on gradle build tools 1.1, so they are all under src/test
. androidTest
should have your Espresso tests instead. I also had to add the dagger dependency to testCompile
on the build.gradle file like this:
testCompile 'com.google.dagger:dagger:2.0-SNAPSHOT'
testCompile 'com.google.dagger:dagger-compiler:2.0-SNAPSHOT'
@felipecsl thanks for the info. Alas, it turned out I had a compilation error which seemed to be preventing the Dagger class from being generated.
What do you guys think about this solution: https://stackoverflow.com/questions/26939340/how-do-you-override-a-module-dependency-in-a-unit-test-with-dagger-2-0/29996385#29996385
Here's sample project: https://github.com/tomrozb/dagger-testing
The proposed solution is based on extending the production components in test code. This seems as a workaround for v2.0.0.
public class App extends Application {
private AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerApp_AppComponent.create();
}
public AppComponent component() {
return mAppComponent;
}
/**
* Visible only for testing purposes.
*/
// @VisibleForTesting
public void setTestComponent(AppComponent appComponent) {
mAppComponent = appComponent;
}
@Singleton
@Component(modules = StringHolderModule.class)
public interface AppComponent {
void inject(MainActivity activity);
}
@Module
public static class StringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder("Release string");
}
}
}
In test code:
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
@Module
static class TestStringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder(TEST_STRING);
}
}
// Before activity is created
((App) application).setTestComponent(mTestAppComponent);
@tomrozb the problem is that you are changing your production code so that it knows about test which is generally something you try to avoid. It couples production and tests in a very bad way.
The problem indeed is that Dagger 2 requires manual creation of the component inside activities and there is no way to access the component / injector when it is created. It looks like there Dagger 2 would need one more level of indirection to create a component.
I would suggest a very hacky approach here, but that could work and would offer some advantages :
This is hacky but it would actually fit well in the Dagger 2 design, would avoid to couple testing and production code, would be side-effect free (as the module is consumed by each test entirely).
@cgruber Would do think of this approach ?
This is the issue that has stopped me from migrating to Dagger 2 thus far.
@stephanenicolas am I wrong that in your example anything that happens in onCreate would be called with the real dependencies?
ActivityController<Carousel> carouselActivityController = Robolectric.buildActivity(Carousel.class).create();
Carousel carousel = carouselActivityController.get();
di.DbTestAppComponent dbTestAppComponent = ((DbTestApplication) carousel.getApplicationContext()).getComponent();
dbTestAppComponent.inject(carousel);
carousel.init(null); //call anything that is done after the injection in onCreate
This essentially moves any setup to later in the lifecycle that requires dependencies, no?
The solution from @stephanenicolas is the cleanest one, in my opinion, as you don't expose any setter on the target entities/activities to use your mocked component/module. But it's still not perfect as it's injecting twice.
This is making me avoid the migration :(
Best solution IMO is the one from @pyricau: https://groups.google.com/d/topic/dagger-discuss/FtbWILcoqHM
Disclaimer: I haven't yet really used Dagger, and never wrote such tests.
The migration guide states:
Modules that use overrides and rely on dependency injection should be decomposed so that the overriden modules are instead represented as a choice between two modules.
An example by the Dagger developers would be nice. Even if I move the dependency which I want to replace in test scope into a separate module and create a subclass of that module it wouldn't be possible since Dagger doesn't allow to subclass modules and override provides methods. Or what is meant by "choice between two modules"?
I agree, this part of the migration docs is really not clear. I read it multiple times and couldn't get it.
2015-06-10 12:25 GMT-07:00 Sven Jacobs notifications@github.com:
The migration guide http://google.github.io/dagger/dagger-1-migration.html states:
Modules that use overrides and rely on dependency injection should be decomposed so that the overriden modules are instead represented as a choice between two modules.
An example by the Dagger developers would be nice. Even if I move the dependency which I want to replace in test scope into a separate module and create a subclass of that module it wouldn't be possible since Dagger doesn't allow to subclass modules and override provides methods. Or what is meant by "choice between two modules"?
— Reply to this email directly or view it on GitHub https://github.com/google/dagger/issues/110#issuecomment-110885265.
I have created a little injection library as a way to break the compile time dependency on the @Module from production code to solve this very problem. Most of the examples I see for Dagger2 on Android to me are little better than directly instantiating classes in your activity, because they are compile time bound to a particular module and do not allow switching to a different module.
https://gitlab.com/NobleworksSoftware/AndroidInjectorFramework
@dalewking , you should create a sample to demonstrate how it is related to the issue.
@stephanenicolas I know I definitely do. I need to find an existing library to fork and apply the library.
However when applying the library I created to a work project I ran into issues in the case where you have one class that extends another class that also has its own injections. My technique would not compile because both classes wanted to extend Injectable but with different type arguments so am having to rethink the solution.
I'm more and more coming back to the idea of using reflection to generate a map from class to methods on the component like in https://github.com/konmik/Dagger2Example/blob/master/app/src/main/java/info/android15/dagger2example/Dagger2Helper.java. I know as soon as you say reflection in connection with dependency injection many people will stop listening to you since they had such a bad experience with reflection in Guice on Android. But as this article shows this time needed for this little bit of reflection is negligible: https://github.com/konmik/konmik.github.io/wiki/Snorkeling-with-Dagger-2#performance. People also equate using reflection with "injection can fail at runtime", but since you want to actually allow the decision of what gets injected to be able to made at runtime then to me it is a given that injection could fail at runtime.
Hi all,
It's been a while... Did anyone come up with something to solve this issue properly?
Thank you.
@mheras I do have a solution, but it is not quite ready for public consumption (needs readme, documentation, example, etc.)
It basically has 2 parts one is an annotation processor that generates a generic wrapper class for each component and subcomponent that lets you do injection using generic methods. It is basically my own rewrite of the injection portion of the bullet library by tbroyer (https://github.com/tbroyer/bullet). Bullet is a great concept, but I found the implementation classes that it generated totally unacceptable. I would much rather this actually be done in Dagger 2 because it would be much easier there (see https://github.com/google/dagger/issues/213)
The other half is what I described earlier, but now is greatly simplified, which basically hides injection away behind a static injection service. You create an implementation of an injector interface:
public interface Injector
{
void inject(Context context, Object target);
}
There is a class with static methods to register the instance of the interface to use for the app and to do injection for all the injection challenged Android classes.
So in your activities, fragments, views, services, etc. you only have to add a line in the appropriate place of the form:
InjectionService.inject(this);
For unit tests, I just register a mock version of the Injector interface in my test setup code.
I will try to get it pushed and somewhat reviewable this week.
@dalewking That sounds REALLY good... Let me know if you need help testing it!
I came up with a workaround to allow for module specific overrides which seems better than the multiple source set approach. It still seems a bit hacky though since it's kind of going against the module apis. In each of your modules where you would like to override a dependency create a method which provides the dependency and modify your Provides method to return that method. Now you can create a module which extends this module and override the method to provide a test/mock dependency. Here is an example:
public class OverriddenModules {
@Component(modules = {
AppModule.class,
NetworkModule.class
})
public static interface AppComponent {
void inject(Application application);
}
@Module
public static class AppModule {
@Provides
AppDependency provideAppDependency() {
return getAppDependency();
}
protected AppDependency getAppDependency() {
return new AppDependency();
}
}
@Module
public static class NetworkModule {
@Provides
NetworkDependency provideNetworkDependency() {
return getNetworkDependency();
}
protected NetworkDependency getNetworkDependency() {
return new NetworkDependency();
}
}
@Component(modules = {
TestApplicationModule.class,
TestNetworkModule.class
})
public static interface TestAppComponent extends AppComponent {
void inject(TestApplication testApplication);
}
@Module
public static class TestAppModule extends AppModule {
@Override
protected AppDependency getAppDependency() {
return Mockito.mock(AppDependency.class);
}
}
@Module
public static class TestNetworkModule extends NetworkModule {
@Override
protected NetworkDependency getNetworkDependency() {
return Mockito.mock(NetworkDependency.class);
}
}
}
@mattkranzler5 I am still getting the message "@Provides methods may not override another method." when I try your solution. Any idea ?
@JorisPotier did you make sure to override your method that is NOT marked @Provides? In your subclass module you cannot override your provides methods, only the delegate method, as shown above.
@mattkranzler5 :+1: it works better now thanks
except that I add to modify your TestAppComponent like this, because I add some build errors in my case, telling me that I did not implement some Providers for some dependencies.
@Component(modules = {
ApplicationModule.class,
NetworkModule.class
})
public static interface TestAppComponent extends AppComponent {
}
Hmm... that shouldn't be necessary. If you do that the test component won't have the proper test module composition. As long as the modules defined in the test component are subclasses of the modules defined in the regular component it should work.
@mattkranzler5 You don't have to create a specific test component, just pass your test modules in explicitly (using builder().applicationModule(new TestApplicationModule())
) rather than letting Dagger create them for you (e.g. when using create()
); and that way you don't have to create new methods an can just override the @Provides
methods.
@tbroyer the problem is that Dagger 2 won't let you override the @Provides
methods.
@mattkranzler5 Just remove the @Module
annotation as well.
@tbroyer how would you handle injecting test classes if you don't provide a test component? If your test source is outside of your main source you wouldn't be able to add inject methods to your main component nor would you want to.
@mattkranzler5 This is not what your example showed. But given that you're not "reshaping" the graph in your modules with that approach, you can still declare a test component referencing the prod modules for the static analysis, and give it the test modules at runtime.
What many of you seem to forget is that Dagger 2 builds the whole dependency graph at build time based on static analysis of the classes (and this is precisely what makes it extremely lightweight at runtime); so you cannot change the shape of the graph at runtime, and if you need a differently-shaped graph for your tests, then you need a different graph: different component(s) and different module(s), referencing different injectable classes.
BTW, I'm curious whether one couldn't/shouldn't use Guice during tests (some, at least) with its new DaggerAdapter
.
@tbroyer I just verified that as long as you don't annotate your module subclasses as @Module
and the overridden @Provides
classes everything works and you can override your dependencies for testing purposes. Thank you for pointing this out!
If that works, could a sample illustrate the technique ? Le 2015-09-10 12:19, "Matt Kranzler" notifications@github.com a écrit :
@tbroyer https://github.com/tbroyer I just verified that as long as you don't annotate your module subclasses as @Module and the overridden @Provides classes everything works and you can override your dependencies for testing purposes. Thank you for pointing this out!
— Reply to this email directly or view it on GitHub https://github.com/google/dagger/issues/110#issuecomment-139299804.
@stephanenicolas here is an updated example:
public class OverriddenModules {
@Component(modules = {
AppModule.class,
NetworkModule.class
})
public static interface AppComponent {
void inject(Application application);
}
@Module
public static class AppModule {
@Provides
AppDependency provideAppDependency() {
return new AppDependency();
}
}
@Module
public static class NetworkModule {
@Provides
NetworkDependency provideNetworkDependency() {
return new NetworkDependency();
}
}
@Component(modules = {
ApplicationModule.class,
NetworkModule.class
})
public static interface TestAppComponent extends AppComponent {
void inject(TestApplication testApplication);
}
public static class TestAppModule extends AppModule {
@Override
AppDependency provideAppDependency() {
return Mockito.mock(AppDependency.class);
}
}
public static class TestNetworkModule extends NetworkModule {
@Override
NetworkDependency provideNetworkDependency() {
return Mockito.mock(NetworkDependency.class);
}
}
}
NOTE: I did find one issue which requires a workaround still with this approach. Let's say you have a dependency that has an injected constructor and you don't have a @Provides
annotated method in your parent module. If you'd like to override that dependency in a module subclass it won't work since you can't have @Provides
methods in a class not annotated with @Module
. Therefore you must add a @Provides
method to your parent module then you can override it in your module subclass.
Just create a test @Module
for it that you reference from your test component. Note that in this case you need/use a differently-shaped graph.
@tbroyer that would work in some cases. In our case we have a differently scoped subcomponent which we are overriding for testing purposes during instrumentation tests in our Android app. We need to be able to swap out that scoped component for one that mocks out certain dependencies for our tests. We wouldn't be able to do what you're suggesting since it would require a differently shaped graph.
@mattkranzler5 that's a great hack. Worked perfectly (after tweaking it for a bit, your code sample is not 100% correct I think). Thanks a lot!
There is also an alternative mentioned recently in Android Weekly : http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
@mattkranzler5 , the test and prod components can be declared in different trees (test vs. main), right ?
Also @mattkranzler5 , I didn't fully get the edge case. What you did is exactly providing a way to override a dependency declared in a parent module, exactly what was searched for...
We have some documentation on strategies for doing testing of classes and applications that use Dagger. We're working on getting that documentation synced out here.
In the meantime, see the discussion starting at https://github.com/google/dagger/issues/186#issuecomment-163309550.
Apologies if this is a dup (couldn't see an open issue).
Is there any standard way to do testing. Overriding
@Provides
or extending module classes.I know it was discussed by Jake at Devvox that it still needs fleshing out.
What are the current thoughts? This for me is the only real blocker. I can work around the limitations, but it feels like yak-shaving.