Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Mock an injected object that is not declared in Module?

For a dagger2 module

@Module
public class MyModule {
    @Provides @Singleton public RestService provideRestService() {
        return new RestService();
    }

    @Provides @Singleton public MyPrinter provideMyPrinter() {
        return new MyPrinter();
    }
}

We could have the test module as Test

public class TestModule extends MyModule {
    @Override public MyPrinter provideMyPrinter() {
        return Mockito.mock(MyPrinter.class);
    }

    @Override public RestService provideRestService() {
        return Mockito.mock(RestService.class);
    }
}

However if for a class as below that is not declared in the dagger module...

public class MainService {
    @Inject MyPrinter myPrinter;

    @Inject public MainService(RestService restService) {
        this.restService = restService;
    }
}

How do I create a mock of MainService as above.

Note, I'm not planning to perform test for MainService as per share in https://medium.com/@fabioCollini/android-testing-using-dagger-2-mockito-and-a-custom-junit-rule-c8487ed01b56#.9aky15kke, but instead, my MainService is used in another normal class that I wanted to test. e.g.

public class MyClassDoingSomething() {
    @Inject MainService mainService;

    public MyClassDoingSomething() {
        //...
    }

    // ...
    public void myPublicFunction() {
        // This function uses mainService
    }
}
like image 804
Elye Avatar asked Sep 12 '16 12:09

Elye


1 Answers

This is definitely not answering your question, but in my honest opinion it is related, it's helpful and too big for a comment.

I'm often facing this question and I end always doing "Constructor dependency injection". What this means is that I no longer do field injection by annotating the field with @Inject but pass the dependencies in the constructor like so:

public class MyClassDoingSomething implements DoSomethig {
    private final  Service mainService;

    @Inject
    public MyClassDoingSomething(Service mainService) {
        this.mainService = mainService;
    }
}

Notice how the constructor now receives the parameter and sets the field to it and is also annotated with @Inject? I also like to make these classes implement an interface (also for MyService) - Amongst several other benefits I find it makes the dagger module easier to write:

@Module
public class DoSomethingModule {
   @Provides @Singleton public RestService provideRestService() {
       return new RestService();
   }

   @Provides @Singleton public MyPrinter provideMyPrinter() {
       return new MyPrinter();
   }

   @Provides @Singleton public Service provideMyPrinter(MyService service) {
       return service;
   }

   @Provides @Singleton public DoSomethig provideMyPrinter(MyClassDoingSomething something) {
       return something;
   }
}

(This assumes that MyService implements or extends Service)

By now it seems you already know that dagger is able to figure out the dependency graph by itself and build all the objects for you. So what about unit testing the class MyClassDoingSomething? I don't even use dagger here. I simply provide the dependencies manually:

public class MyClassDoingSomethingTest {
   @Mock
   Service service;

   private MyClassDoingSomething something;

   @Before
   public void setUp() throws Exception {
      MockitoAnnotations.init(this);
      something = new MyClassDoingSomething(service);
   }
   // ...
}

As you see, the dependency is passed through the constructor manually.

Obviously this doesn't work if you're coding something that doesn't have a constructor that can be invoked by you. Classical examples are android activities, fragments or views. There are ways to achieve that, but personally I still think you can somehow overcome this without dagger. If you are unit testing a view that has a field @Inject MyPresenter myPresenter, usually this field will have package access that works fine in the tests:

public class MyViewTest {
   @Mock MyPresenter presenter;

   private MyView view;

   @Before
   public void setUp() throws Exception {
      MockitoAnnotations.init(this);
      view.myPresenter = presenter;
   }
}

Note that this only works if both MyViewTest and MyView are in the same package (which often is the case in android projects).

At the end of the day if you still want to use dagger for the tests, you can always create "test" modules and components that can inject by declaring methods in the component like:

@Inject
public interface MyTestComponent {
   void inject(MyClassDoingSomething something);
}

I find this approach ok-ish, but throughout my development years I prefer the first approach. This also has reported issues with Robolectric that some setup in the build.gradle file is required to actually make the dagger-compiler run for the tests so the classes are actually generated.

like image 193
Fred Avatar answered Oct 06 '22 17:10

Fred