Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
    }

}
like image 826
Andy Res Avatar asked Nov 05 '16 20:11

Andy Res


1 Answers

Property injection is like that is not so easy to test. In this case, 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 probably shouldn't be using DaggerNetworkComponent.inject(this) inside RssListPresenter. Instead you should be configuring dagger so that when it injects members into your top-level classes (Activity, Fragment, Service) it can access the object graph and create an instance of your RssPresenter.

Why only put injectors in Activity and Service and not in something like RssListPresenter? These are classes that are instantiated by the Android system and so you have no choice but to use injectors.

To clarify, Activity, Fragment etc. are ideal 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);
}
like image 98
David Rawson Avatar answered Oct 24 '22 19:10

David Rawson