Unit Testing with Dagger 2
Dagger 2 has many advantages over the original Dagger. It’s powered by annotation processing, so it’s much safer and more performant. It also has the concept of components, so your DI configuration has a more clear public API instead of just a nebulous object graph.
Testing however can be quite tricky. Dagger 1 had a very simple override system that made it very easy to replace specific dependencies for tests. Dagger 2 lacks this and doesn’t really provide any kind of opinion on how to write tests.
While there is no correct answer for the best approach with Dagger 2, I do think there are some common pitfalls that should be avoided.
Testing objects that don’t get dependencies in their constructor
This is where it’s the most tricky.
Some objects cannot use constructor dependencies for a few reasons:
- The objects might be created by a system out of your control (think Android activities and fragments).
- You intentionally want to pass in dependencies at specific point in an object’s lifecycle.
- You want the object to be able to have dependencies that depend on itself.
When testing these objects, it’s best to not try to leverage Dagger 2.
With the original Dagger, this wasn’t the case. It used to be very easy to just override dependencies in the object graph. With Dagger 2, if you try this approach, you’ll end up with something very clunky. You’ll need to create an entire Dagger 2 component to be used in tests. If the component you’re creating are at a very high up scope, it’s common to have lots of module dependencies (even if your test doesn’t need them). In addition, if you subclass a module, you can’t change method signatures. This wasn’t the case in Dagger 2, because you could override the graph with any module.
Instead of taking this approach, I think it’s best to leave Dagger 2 out of the equation when unit testing these objects. A simple approach at this is to just set the fields manually in your test. This doesn’t require any complex Dagger setup, the fields are already package-private or public, and it only requires you to create dependencies for the class you are testing, not the entire component.
If manually setting fields weirds you out, it’s also possible to code-gen constructor-like utility methods using annotation processing. While this takes a bit of work to setup initially, it does allow you to enforce consistent testing conventions in a cleaner way.
Testing objects that do get dependencies in their constructor
This might be obvious or self-explanatory, but if you have an object that uses constructor injection for its dependencies then there is no need at all for its tests to use Dagger. Just pass whatever dependencies you want to use in the tests via the constructor.
Testing complicated @Provides methods
Another common Dagger (1 or 2) or general dependency injection issue is meaty provider methods that have a fair amount of configuration logic. In an Android app for example, some providers methods might check the current build type, and configure objects differently based on it.
Once these methods get fairly large, you might want to test them. However, it’s kind of strange to unit test a Dagger module. It’s more configuration code, and not something that’s usually tested.
If you end up with large provider methods, I think it’s best to spin the creation of it out into something else that is tested so Dagger modules can be as simple as possible (and ignored from coverage tools).