android retrofit2, dagger2 unit test

1.2k Views Asked by At

I learn how to test the presenter layer of MVP architecture in android, my presenter using retrofit 2 and in my activity I used dagger 2 as dependency injection to my presenter, this is my Dagger and presenter injection looks like:

@Inject
AddScreenPresenter addScreenPresenter;

This is the Dagger builder :

DaggerAddScreenComponent.builder()
            .netComponent(((App) getApplicationContext()).getNetComponent())
            .addScreenModule(new AddScreenModule(this, new ContactDatabaseHelper(this)))
            .build().inject(this);

and this is my presenter constructor :

@Inject
public AddScreenPresenter(Retrofit retrofit, AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
    this.retrofit = retrofit;
    this.view = view;
    this.contactDatabaseHelper = contactDatabaseHelper;
}

I have write the unit test class and mock the Retrofit class, but when I run it, the error appears :

Mockito cannot mock/spy following:

- final classes - anonymous classes - primitive types

This is the test class :

@RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {

private AddScreenPresenter mAddPresenter;

@Mock
private Retrofit mRetrofit;

@Mock
private Context mContext;

@Mock
private AddScreenContact.View mView;

@Mock
private ContactDatabaseHelper mContactDatabaseHelper;


String firstName, phoneNumber;

Upload upload;


@Before
public void setup() {
    mAddPresenter = new AddScreenPresenter(mRetrofit, mView, mContactDatabaseHelper);

    firstName = "aFirstName";

    phoneNumber = "998012341234";

    Uri path = Uri.parse("android.resource://"+BuildConfig.APPLICATION_ID+"/" + R.drawable.missing);

    upload = new Upload();
    upload.title = firstName;
    upload.description = "aDescription";
    upload.albumId = "XXXXX";
    upload.image = new File(path.getPath());
}

@Test
public void checkValidationTest() {
    verify(mAddPresenter).checkValidation(firstName, phoneNumber);
}


@Test
public void uploadMultiPartTest() {
    verify(mAddPresenter).uploadMultiPart(upload);
}

}

this is my module :

@Module
public class AddScreenModule {

private final AddScreenContact.View mView;
private final ContactDatabaseHelper mContactDatabaseHelper;

public AddScreenModule (AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
    this.mView = view;
    this.mContactDatabaseHelper = contactDatabaseHelper;
}

@Provides
@CustomScope
AddScreenContact.View providesAddScreenContactView() {
    return mView;
}

@Provides
@CustomScope
ContactDatabaseHelper providesContactDatabaseHelper() {
    return mContactDatabaseHelper;
}
}

I know that Retrofit class is a final class, and now I stuck and don't know how to create the presenter object in my test class. Please help me, how to create the object of the presenter class with retrofit in the constructor. Feel free to ask if my question is not clear enough, and thank you very much for your help.

1

There are 1 best solutions below

3
On

Personally I'd make the presenter not depend on the Retrofit class but rather on the services created by Retrofit - These are mockable.

It's hard to say from the code you posted which services your presenter actually uses, but for the sake of simplicity let's say it uses only one and let's say it's AddsService - This is an interface ready to work with Retrofit. Something like this for example

public interface AddsService {
   @GET(...)
   Call<List<Adds>> getAllAdds();
}

Now you can make your presenter depend on this rather than Retrofit

@Inject
public AddScreenPresenter(AddsService addsService, 
                          AddScreenContact.View view, 
                          ContactDatabaseHelper contactDatabaseHelper){
   this.addsService = addsService;
   this.view = view;
   this.contactDatabaseHelper = contactDatabaseHelper;
}

You now need to provide this dependency. I'm guessing you have also a NetModule since you have a NetComponent, so I assume you can just do:

@Module
public class NetModule {
   // Methods providing Retrofit
   @Provides
   @Singleton
   public AddsService providesAddsService(Retrofit retrofit) {
      return retrofit.create(AddsService.class);
   }
}

Notice how the providesAddsService depends on retrofit? This should be already provided since your presenter is depending on it. You shouldn't need to change anything for that. Dagger is able to figure out how to provide Retrofit to the method providesAddsService.

Please notice also that I'm assuming you can provide these in a Singleton scope. I assume this because in your code you retrieve the component from the application, which should handle the singleton scope.

Now in your tests you can simply mock AddsService and test your presenter.

If your presenter depends on more services, I'd also pass them in the constructor and provide the implementations with Dagger.

As a bonus, let me also say that the retrofit instance and the retrofit services should only be created once (or at least as less times as possible). This is because they're usually expensive operations and you usually always query the same endpoints with different parameters.

EDIT

To answer some of the questions in the comments. First the easy one: How to create the presenter in the test classes? Like you I too try to get away from Dagger during tests, that's why I prefer constructor dependency injection just like you show you're using. So in my test class I'd have something very similar like you:

@RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {

   private AddScreenPresenter mAddPresenter;

   @Mock
   private AddsService addsService;

   // ...

   @Before
   public void setUp() throws Exception {
      mAddPresenter = new AddScreenPresenter(addsService, 
           mView, mContactDatabaseHelper);
      // ...
   }
}

So basically the only difference is that I would pass the mock to the service.

Now the second question: How to call the presenter constructor from the activity? Well you don't... that's the whole idea of dependency injection. You should use dagger to provide your presenter. I think this is already what you do and I guess this is what it's in your activity:

@Inject
AddScreenPresenter addScreenPresenter;

So all you need to do is have a provider method in your module that provides this and is able to inject it.

You can also make the component return the presenter provided by the module:

@Component(...)
public interface AddScreenComponent {
   AddScreenPresenter getPresenter();
}

And then in your activity you'd do something like:

addScreenPresenter = component.getPresenter();

I don't really have any preference here. The key point is to understand that you should not build the objects yourself (unless inside @Modules). As a rule of thumb any time you see new being used that means you have a tight dependency on that object and you should extract it to be injected. So this is why you should avoid creating the presenter inside your activity. It will couple the presenter to the activity.