Testing Dagger-injected Fragments and Activities

Nikita Borodikhin
AndroidPub
Published in
6 min readApr 23, 2018
Photo by rawpixel.com on Unsplash

Hello, contemporary Android developer. I would like to talk about non-trivial use of Dagger to test Android application components as units.

During the past few years, we were delighted to incorporate a few engineering practices into our mainstream development. One such practice is splitting our apps into layers (MVP and alike), which helps us to reduce the complexity of each layer and isolate their internals from other layers. The other one is dependency injection (mainly Dagger). And last year Dagger 2.11 brought us integration with Android out of the box, which significantly reduced entry level to using dependency injection.

Nowadays we do write tests. We write unit tests to test our presenters. We test business logic and database migrations. When testing non-UI layers, we manually inject all the dependencies, which are controlled by our test. We use Espresso tests to test our production activities and run integration test scenarios — sometimes we use production app with production Dagger code, and sometimes we use flavored builds and configure Dagger to inject fake dependencies.

Problem

Usually, one kind of testing is missing in this picture: activity unit-testing in isolation. You can ask “why do we need that — we already do integration testing,” and you are partially right. Integration tests are important, and they are an essential part of the process. However, they tend to be very complex, as they have to configure the whole environment in which activity exists — databases, network communication and so on. It is very hard to control what your activity needs for itself and what is just a dependency of dependency. Sometimes integration tests even do not do any special configuration at all — they use real database implementation, real network layer and real servers with some test accounts. That’s fine, but it makes it hard to test corner cases, and flaky network could result in failed activity test. There is a direct parallel with unit testing — it’s better to have a comprehensive test suite that tests units in isolation and a few integration tests than the same amount of flaky integration tests.

However, as our apps grow bigger and more complex, activities have more and more external dependencies. We know how to deal with a complex activity — extract presentation layer into an object of its own with a well-defined interface to its counterparts, and inject those as dependencies. Dagger takes care of resolving all the dependencies. Extracted presenters (or their equivalent) do not depend on UI and are unit-testable. We can write a plain Java unit test that would instantiate them and provide mock dependencies to model presenter’s environment. Win? Win! But what about the activity itself? There is not much logic there left, but there is some, and we definitely can write an Espresso test for that code. The test would inject a fake presenter and check how activity interacts with it. But now we have a problem: how to inject presenter into activity?

Let’s take a step aside and test a Fragment first. In some sense, it is easier to work with fragments: they are similar to Activities, but reside a little bit below activities, and do not require application-level manifest entry to work with. We’ll get back to activities a bit later.

Fragment

Let’s start with a single Fragment. Although it has to be hosted by an activity, but, thanks to Dagger, we can make it relatively independent from the activity — if it gets all the data injected, it does not need to be hosted by the very same application activity. The idea is simple: let’s test our fragment with a dummy test activity instead of the real one.

Dagger-injected fragments use Dagger two ways:

  1. As an injector that injects fields and methods of the fragment
  2. As a run-time resolver that finds appropriate injector (onAttach() invokes AndroidSupportInjection.inject(this), which does the job)

Guess what? For unit-testing fragment, we don’t need Dagger-as-injector, and that’s exactly what Dagger documentation says: “Don’t use Dagger for unit testing”! But since our fragment descends from DaggerFragment, we stuck to using Dagger-as-resolver. That leads to using Dagger-as-injector unless we intercept Dagger injection calls. Can we do that?

Let’s see how Dagger does resolving. It looks for an object which implements HasSupportFragmentInjector() interface along the fragment’s natural chain of ownership: parent fragments, activity, and application:

Our test has to inject its injector into one of the objects along that chain. We can skip fragments — we would reduce the problem to itself and application (we need an activity to host fragment anyway). So, let’s create a simple activity to host the fragment under test and let our Espresso test set up test-specific injector in the activity. Now, as we know what we need to do, it is simple:

Fragment test has to do some ceremony around activity launch, but it not too complicated:

Congratulations! Now we have a simple test for a fragment that uses test activity to host the fragment under test. We can reuse the activity to test any fragment that uses Dagger to get its dependencies. If you ask me, this is amazing.

There is one caveat though. Test activity has to be declared in an application manifest. Thanks to the manifest merger, there are three main places you can do that:

  • Main manifest
  • Debug manifest
  • Flavored build manifest

It is up to you which one to use, but I would go for the debug manifest. First, we usually do not unit-test our release builds, so unit test code does not need to be in the main codebase. Second, unit tests are self-contained and should not have anything flavor-specific; therefore they can be in a generic non-flavored code. And third, if you use Debug manifest and have a debug menu of some sort, you can add entry points to launch your fragments with a predefined environment for manual testing. Having such a playground to manual test animations of UI without business logic can be an indispensable help during UI development.

Activity

Now let’s get back to activities. Activities have only one natural level above them — application. Similarly to the fragment case, we would want to use the same application activity to implement our test injector injection.

Quite similar to the activity/fragment case, Application has to implement HasActivityInjector interface. Unfortunately, Dagger authors chose to make it hard for us to use DaggerApplication for testing, as it uses DispatchingAndroidInjector<Activity> instead:

DispatchingAndroidInjector is a nice little class that implements Dagger machinery of finding appropriate injector factory for a type. Normally, Dagger creates an instance of such an injector and injects it into the application. We want to use our own instance instead, so we have to inject it into the application instance ourselves. What application? There are two approaches:

  • use completely different application class
  • modify main application class to allow tests to set up a specific activity injector

Using a separate application class for instrumentation testing is well discussed and can be appealing for automated tests, but there are situations you might need to use the real application (e.g., to use activities set up for manual testing). There are two ways to achieve that: reimplement the code of DaggerApplication in your activity but with more relaxed type (there should be no problem with that), or wrap your test injector into a fake DispatchingActivityInjector. Here is the example of the second approach. Application code:

And here is an activity test:

Although activity testing is conceptually the same as fragment testing, setting up an injector is different, and takes a little bit more code, but that could be solved by a custom rule. Such a rule would extend ActivityTestRule and do all the dirty work under the hood.

Conclusion

Thanks to Dagger Android support, we could make a simple setup that allows us to apply unit testing to run tests against our UI components in isolation, with all the benefits of such a testing. We can easily test UI-specific behaviors such as animations, UI event bindings, rendering of any possible data, and checking corner-case UI states without hitting actual servers. This is the missing layer in the integration/UI/non-UI testing pyramid. With minimal extra code, we now can write tests for fragments adding just a test class (no test activities required!). With just a minimal change to the application class, we can run hermetic tests against activities.

Sample repository can be found here.

Happy testing!

--

--