Andy Res Andy Res - 27 days ago 8
Java Question

Mocking an injected field in unit tests

I have a

Presenter
class which uses a field injected through Dagger, it looks something like this:

public class RssListPresenter {

@Inject
RssService rssService; // <-- injected field

public RssListPresenter() {
setupDI();
}

private void setupDI() {
DaggerNetworkComponent.builder()
.networkModule(new NetworkModule())
.build()
.inject(this);
}

public void loadItems() {
Rss rss = rssService.getRssFeed()
// ....
}
}


Everything works fine. Now, I would like to unit test the
RssListPresenter
class. The question is how do I provide a mock
RssService
to the presenter?

Ofcourse I can add a new method
setRssService(RssService rssService)
to the presenter and use it to provide the mock from unit tests, but adding this method just for unit tests does not feel right. What would be the correct way to handle this?

For completeness here are the module and component declarations:

@Singleton
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(RssListPresenter presenter);
}

@Module
public class NetworkModule {

@Provides
Retrofit provideRetrofit() {
// ...
}

@Provides
@Singleton
RssService providePcWorldRssService(Retrofit retrofit) {
return retrofit.create(RssService.class);
}

}

Answer

Property injection is not amenable for testing. Constructor injection is much better. Refactor your constructor to look like this:

private final RssService rssService;

@Inject
public RssListPresenter(RssService rssService) {
        this.rssService = rssService;
}

Now you can test it easily:

//mocks
RssService mockRssService;

//system under test
RssListPresenter rssListPresenter;

@Before
public void setup() {
    mockRssService = Mockito.mock(RssService.class);
    rssListPresenter = new RssListPresenter(mockRssService);
}

You shouldn't be using DaggerNetworkComponent.inject(this) inside RssListPresenter. Instead you should be configuring dagger so that when it injects members your top-level classes (Activity, Fragment, Service) it can access the object graph and create an instance of your RssPresenter.

To reiterate, Activity, Fragment etc. are valid injection targets. RssListPresenter etc. are injected dependencies. You need to configure the dependency injection framework, dagger, so that it can provide the correct dependencies to inject into the injection targets.

So you will also need to write a @Provides method for RssListPresenter

@Provides provideRssListPresenter(RssService rssService) {
    return new RssListPresenteR(rssService);
}
Comments