Dagger: Testing/Overriding Modules

Created on 27 Jan 2015  Â·  70Comments  Â·  Source: google/dagger

_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.

documentation

Most helpful comment

I still think there is no easy and clean way to do it in Android...

All 70 comments

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 :

  • inside the component Dagger2 could look for a static override/test module override (a static field) : if the module is present then it will be added and have precedence over the production modules. And the static field would be set to null right after being used.
  • during production, you should never set the static module
  • during test, you set it at the beginning of each tests. The module is used only once, then consumed (set to null by the component).

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 [email protected]:

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" [email protected] 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.

Send the link mike please

Le dim. 13 déc. 2015 19:58, Mike Nakhimovich [email protected] a
écrit :

Not sure if this doesn't fit someone's use case but here's a gist showing
a really simple way to override dependencies in dagger2

—
Reply to this email directly or view it on GitHub
https://github.com/google/dagger/issues/110#issuecomment-164316500.

What @netdpb suggests works great. Here's what it looks like.

In prod:

@Component(modules={AppModule.class, ProdModule.class})
public interface AppGraph {
  void inject(MyApp app);
}

@Module(modules={ /** List all the modules that are shared here **/ }
public class AppModule{}

@Module(/** List modules containing only bindings that should be redefined **/)
public class ProdModule {}

public class MyApp extends Application {

  public void onCreate() {
    super.onCreate();
    AppGraph graph = createAppGraph();
    graph.inject(this);
  }

  protected AppGraph createAppGraph() {
    return DaggerAppGraph.builder().build();
  }
}

In test:

@Component(modules={AppModule.class, TestModule.class})
public interface TestAppGraph extends AppGraph {
}

@Module(/** List modules containing redefined  bindings **/)
public class TestModule {}

public class MyTestApp extends MyApp {
  @Override protected AppGraph createAppGraph() {
    return DaggerTestAppGraph.builder().build();
  }
}

Basically, the workaround is that if in Dagger 1 you had Module B extend Module A, in Dagger 2 you add a Module C and move anything that isn't overridden by module B from module A to module C.

I know this works and is what we had to do to our app, but Dagger 2 really should support the overrides like in Dagger 1, it is much more convenient.

@pyricau
How do you set the created MyTestApp when you need it in an ActivityInstrumentationTestCase2 for example?

@PaulWoitaschek you can do it via custom instrumentation test runner, see http://artemzin.com/blog/how-to-mock-dependencies-in-unit-integration-and-functional-tests-dagger-robolectric-instrumentation/ for more details

Does anyone use DaggerMock? Maybe this can make overriding a module easier.
https://github.com/fabioCollini/DaggerMock

What @pyricau says sound great. I have the same question as @PaulWoitaschek. I know it's been answered by @artem-zinnatullin, but using a custom instrumentation test runner will use that MyTestApp for all instrumented tests (I want to use different TestModule's in different tests)

Is there a way to use @pyricau 's solution only for some of the instrumented tests in Android? I'm specifically thinking of a way of creating a JUnit Rule to achieve this behavior (or similar) per test.

FWIW, there's now some "official" documentation up at https://google.github.io/dagger/testing.html

As @tbroyer said, the official documentation lists some good points. The concept outlined are more generic than for android-specific use cases. For someone like me, i.e. leveraging dagger 2 on a non-android application, the official testing documentation was a lot more readable.

The documentation makes it a lot better but I still can't figure out how to do that when we have component dependencies/subcomponent.
Basically, if I have an AppComponent (containing the context, for example), and a NetworkComponent that requires a context (that is provided by a module of AppComponent).
I want to replace the NetworkComponent by a MockNetworkComponent.
I tried both solution (component dependencies/subcomponent), and none worked :

  • With NetworkComponent being a subcomponent, MockNetworkComponent needs to have the same module than NetworkComponent... (so I can't use MockNetworkModule, I have to use NetworkModule that provide the real implementation).
  • WIth component dependencies, I have to instantiate the DaggerNetworkComponent directly so it's hard to use DaggerDebugNetworkComponent instead.

Does someone have a solution ? I'm not far from trying Dagger 1 at this point.

@NitroG42 Difficulty with testing is one of the reasons Square (the company that made Dagger 1) still hasn't changed to Dagger 2.

We have quite a bit of Dagger 2, actually.

On Wed, Apr 20, 2016 at 10:18 PM Scott Pierce [email protected]
wrote:

@NitroG42 https://github.com/NitroG42 Difficulty with testing is one of
the reasons Square (the company that made Dagger 1) still hasn't changed to
Dagger 2.

—
You are receiving this because you are subscribed to this thread.

Reply to this email directly or view it on GitHub
https://github.com/google/dagger/issues/110#issuecomment-212696425

My mistake. I had assumed differently a few months ago from this thread: https://github.com/JakeWharton/u2020/issues/158

I'm closing this issue since we've published our testing patterns in the link that @tbroyer added above. If you have further questions, definitely feel free to reach out.

So why did you close #213 at the same time? Nothing has been resolved regarding #213 to my knowledge. The testing patterns link might be useful to someone doing a desktop or server app, but does not help one iota with Android, which is what I was addressing in #213

We have seen several Android apps using those patterns.

I still think there is no easy and clean way to do it in Android...

There's probably some work to be done with more samples to explain how to have different configurations in test, dev & release on Android. Overall though, everything works just fine, and it's all documented in some way in this issue.

@mattkranzler5 - I'm trying to follow your pattern in my code, but I'm running into duplicate class errors in compilation. Is this something you've encountered?

@caltseng has a point - the dependency management issues that come up with Dagger is a real problem. Worst case scenario is some future build tanks because the dependency tree gets wack. Which I've seen a lot of.

Anyway, scoping members to default and using class factors is a great way to get around dagger limitations. I'd give that a try and then see if the library is worth the effort.

Other issues you can run into relate to lifecycle. People often end up calling init in the Application class to get around the fact that you have to clean things up properly when onStop/pause/saveinstancestate/destroy are called.

Let's just say your mileage will vary on that.

Was this page helpful?
0 / 5 - 0 ratings