Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Dagger 2 make testing easier on Android?

One of the best advantages of using DI is it makes testing a lot easier (What is dependency injection? backs it too). Most of DI frameworks I've worked with on other programming languages (MEF on .NET, Typhoon on Obj-C/Swift, Laravel's IoC Container on PHP, and some others) allows the developer do register dependencies on a single entry point for each component, thus preventing the "creation" of dependency on the object itself.

After I read Dagger 2 documentation, it sounds great the whole "no reflection" business, but I fail to see how it makes testing easier as objects are still kind of creating their own dependencies.

For instance, in the CoffeMaker example:

public class CoffeeApp {
  public static void main(String[] args) {

    // THIS LINE
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();

    coffeeShop.maker().brew();
  } 
}

Even though you're not explicitly calling new, you still have to create your dependency.

Now for a more detailed example, let's go to an Android Example. If you open up DemoActivity class, you will notice the onCreate implementation goes like this:

@Override 
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
    // Perform injection so that when this call returns all dependencies will be available for use.
   ((DemoApplication) getApplication()).component().inject(this);
}

You can clearly see there is no decoupling from the DI component, to the actual code. In summary, you'd need to mock/stub ((DemoApplication) getApplication()).component().inject(this); on a test case (if that's even possible).

Up to this point, I am aware Dagger 2 is pretty popular, so there is got to be something I am not seeing. So how does Dagger 2 makes testing classes easier? How would I mock, let's say a network service class that my Activity depends on? I would like the answer to be as simple as possible as I'm only interested in testing.

like image 496
Christopher Francisco Avatar asked Sep 09 '15 19:09

Christopher Francisco


People also ask

Why do you use dagger 2?

Dagger 2 walks through the dependency graph and generates code that is both easy to understand and trace, while also saving you from writing a large amount of boilerplate code you would normally need to write by hand to obtain references and pass them to other objects as dependencies.

Is dagger only for Android?

Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android.

How Android Junit framework performs testing of an Android app?

Unit testing is done to ensure that developers write high-quality and errorless code. It is advised to write Unit tests before writing the actual app, you will write tests beforehand and the actual code will have to adhere to the design guidelines laid out by the test.


2 Answers

Dagger 2 doesn't make testing easier

...beyond encouraging you to inject dependencies in the first place, which naturally makes individual classes more testable.

The last I heard, the Dagger 2 team were still considering potential approaches to improving support for testing - though whatever discussions are going on, they don't seem to be very public.

So how do I test now?

You're correct to point out that classes which want to make explicit use of a Component have a dependency on it. So... inject that dependency! You'll have to inject the Component 'by hand', but that shouldn't be too much trouble.

The official way

Currently, the officially-recommended approach to swapping dependencies for testing is to create a test Component which extends your production one, then have that use custom modules where necessary. Something like this:

public class CoffeeApp {
  public static CoffeeShop sCoffeeShop;

  public static void main(String[] args) {
    if (sCoffeeShop == null) {
      sCoffeeShop = DaggerCoffeeShop.create();
    }

    coffeeShop.maker().brew();
  } 
}

// Then, in your test code you inject your test Component.
CoffeeApp.sCoffeeShop = DaggerTestCoffeeShop.create();

This approach works well for the things you always want to replace when you are running tests - e.g. Networking code where you want to run against a mock server instead, or IdlingResource implementations of things for running Espresso tests.

The unofficial way

Unfortunately, it the official way can involve a lot of boilerplate code - fine as a one-off, but a real pain if you only want to swap out a single dependency for one particular set of tests.

My favourite hack for this is to simply extend whichever Module has the dependency you want to replace, then override the @Provides method. Like so:

CoffeeApp.sCoffeeShop = DaggerCoffeeShop.builder()
    .networkModule(new NetworkModule() {
        // Do not add any @Provides or @Scope annotations here or you'll get an error from Dagger at compile time.
        @Override
        public RequestFactory provideRequestFactory() {
          return new MockRequestFactory();
        }
    })
    .build();

Check this gist for a full example.

like image 167
vaughandroid Avatar answered Sep 20 '22 02:09

vaughandroid


"allows the developer do register dependencies on a single entry point for each component" - analogues in Dagger 2 are the Modules and Components where you define the dependencies. The advantage is that you don't define the dependencies directly in your component thus decoupling it so later when writing unit tests you may switch the Dagger 2 component with a test one.

"it sounds great the whole "no reflection" business" - the "no reflection" thing is not the "big deal" about dagger. The "big deal" is the full dependency graph validation at compile time. Others DI frameworks don't have this feature and if you fail to define how some dependency is satisfied you will get an error late at runtime. If the error is located in some rarely used codepath your program may look like it is correct but it will fail at some point in the future.

"Even though you're not explicitly calling new, you still have to create your dependency." - well, you always have to somehow initiate dependency injection. Other DI may "hide"/automate this activity but at the end somewhere building of the graph is performed. For dagger 1&2 this is done at app start. For "normal" apps (as you shown in the example) in the main(), For android apps - in the Application class.

"You can clearly see there is no decoupling from the DI component, to the actual code" - Yes, you are 100% correct. That arises from the fact that you don't control directly the lifecycle of the activities, fragments and services in Android, i.e. the OS creates these objects for you and the OS is not aware that you are using DI. You need manually to inject your activities, fragments and services. At first this seem seems awkward but in real life the only problem is that sometimes you may forget to inject your activity in onCreate() and get NPE at runtime.

like image 20
Ognyan Avatar answered Sep 22 '22 02:09

Ognyan