Providing test doubles with Dagger 1 and Dagger 2

Dagger is a popular dependency injection solution, which works very well with Android, and at the moment is probably a must for any more or less complicated Android project. However, there are things about Dagger that you need to wrap your head around when you're beginning to use it. Also, there are things that don't look that straightforward even after you've used Dagger for some time. One of these things is the way to configure the modules to provide fakes in test mode, and there's been a lot of confusion around this topic. Another reason for the confusion is the way Dagger 1 and Dagger 2 handle overriding - those are a bit different. It hasn't been a long time since I've refactored Dagger 2 modules in my current project, so I decided to dive a little deeper into the problem and describe the solution that I believe is the correct one. Welcome on board!

Overriding modules in Dagger 1

Let's first see how the things worked back in the days of Dagger 1. Imagine we have the following Dagger 1 module called AppModule:

The module provides the @AppScope Context, and an implementation for an interface called GreetingGenerator. In Dagger 1 we would need to create an ObjectGraph using this module, and that's what we'll do in the application class:

Compiler will override any @Provides methods defined in AppModule with the ones we supply in MockGreetingModule. Next we'll create the test version of the application class and will create the graph as follows:

All test classes that query a GreetingGenerator from this application flavor will now get a mock. This is a working solution, but apparently not a very clean one. Let's see what the Javadoc for overrides says:

This is a dangerous feature as it permits binding conflicts
to go unnoticed. It should only be used in test and
development modules.

Well, we're in test mode, so we're kind of fine, but we can do better!

A better approach

Thing is, the ObjectGraph doesn't care how many modules you pass in as long as all @Inject targets are satisfied. That means that the logic of overriding can (and should) be implemented with pluggable modules. First of all, let's separate out the logic for providing the GreetingGenerator into its own module:

Voilà! You see that we pluggedMockGreetingModule to provide GreetingGenerator, and the Dagger compiler is happy. Let's memorize this concept and see how we can apply it in Dagger 2.

Overriding modules in Dagger 2

In Dagger 2 the graph is created by defining a set of interfaces marked with the @Component annotation. An @Component interface is essentially a contract that declares a set of dependencies it can provide. The component relies on a set of modules to implement the logic of providing dependencies, and the modules must be declared explicitly. Let's introduce a component into our example:

Alright, now let's try to create a test module that will allow us to supply a mocked version of GreetingGenerator. First, a hacky approach.

A hacky approach

As you can see from the code snippet above, DaggerAppComponent builder expects us to pass in a module of type AppModule, so our test module will have to have this type. However, if we'll just go on and extend AppModule:

Additionally, Dagger 2 doesn't support overrides anymore, so there's no magic parameter that will let us satisfy the compiler. However, there's a trick to fool the compiler by creating an anonymous module class at runtime:

Now, as I mentioned, a component in Dagger 2 is a contract that relies on a set of modules to provide a set of dependencies. We can go on and extend the component interface, which semantically would mean that we're declaring a different contract that satisfies the same set of dependencies, but is able to rely on a different set of modules - which is exactly what we need:

MockGreetingComponent can provide an implementation of GreetingGenerator and it relies on MockGreetingModule to create it. We can instantiate it in the test version of the application class as follows:

Conclusion

As we've seen, Dagger can be tricky, and there are some non-trivial semantics involved that can take time to grasp. However, a clean Dagger configuration can do wonders for the testability and maintainability of your Android project, so it's worth spending some time to experiment with Dagger's features. Hopefully this article will bring you a tiny step closer to mastering this powerful tool.

I'd be happy to discuss different approaches to solving the aforementioned problem, so please describe your solution in the comments - feedback is very welcome!