android retrofit2,dagger2单元测试

时间:2016-12-21 13:30:50

标签: android unit-testing mockito retrofit2 dagger-2

我学习如何在android中测试MVP架构的演示者层,我的演示者使用改装2,在我的活动中我使用dagger 2作为依赖注入到我的演示者,这是我的Dagger和演示者注入看起来像:

@Inject
AddScreenPresenter addScreenPresenter;

这是Dagger的建设者:

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

这是我的演示者构造函数:

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

我编写了单元测试类并模拟了Retrofit类,但是当我运行它时,出现错误:

Mockito cannot mock/spy following:

- 最后的课程    - 匿名课程    - 原始类型

这是测试类:

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

}

这是我的模块:

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

我知道Retrofit类是最后一个类,现在我卡住了,不知道如何在我的测试类中创建presenter对象。请帮助我,如何在构造函数中使用retrofit创建presenter类的对象。请随意询问我的问题是否不够明确,非常感谢您的帮助。

1 个答案:

答案 0 :(得分:6)

我个人认为主持人不依赖于Retrofit类,而是依赖于Retrofit创建的服务 - 这些都是可以模仿的。

很难说您发布的代码实际上使用了哪些服务代码,但为了简单起见,我们假设它只使用一个,让我们说它是AddsService - 这是一个可以使用Retrofit的界面。像这样的东西,例如

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

现在,您可以让演示者依赖此而不是Retrofit

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

您现在需要提供此依赖项。我猜你还有NetModule,因为你有一个NetComponent,所以我假设你可以这么做:

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

注意providesAddsService如何依赖于改造?这应该已经提供,因为您的演示者依赖它。你不应该为此改变任何东西。 Dagger能够弄清楚如何向方法Retrofit提供providesAddsService

另请注意,我假设您可以在Singleton范围内提供这些内容。我假设这是因为在您的代码中,您从应用程序中检索组件,该组件应该处理单例范围。

现在,在您的测试中,您只需模拟AddsService并测试您的演示者。

如果您的演示者依赖于更多服务,我也会在构造函数中传递它们并使用Dagger提供实现。

作为奖励,我还要说改造实例和改装服务只应创建一次(或至少尽可能少一次)。这是因为它们通常是昂贵的操作,并且您通常总是使用不同的参数查询相同的端点。

修改

回答评论中的一些问题。首先是简单的:如何在测试类中创建演示者?像你一样,我也试图在测试期间远离Dagger,这就是为什么我更喜欢构造函数依赖注入,就像你展示你正在使用它一样。所以在我的测试课中,我会有类似你的东西:

@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);
      // ...
   }
}

所以基本上唯一的区别是我会将模拟传递给服务。

现在第二个问题:如何从活动中调用presenter构造函数?嗯,你不...那就是依赖注入的整个想法。您应该使用匕首来提供演示者。我认为这已经是你做的了,我想这就是你活动中的内容:

@Inject
AddScreenPresenter addScreenPresenter;

所以你需要做的就是在你的模块中有一个提供者的方法来提供这个,并且能够注入它。

您还可以让组件返回模块提供的演示者:

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

然后在你的活动中你会做类似的事情:

addScreenPresenter = component.getPresenter();

我在这里没有任何偏好。关键是要明白你不应该自己构建对象(除非在@Module内)。根据经验,任何时候你看到使用new意味着你对该对象有严格的依赖,你应该提取它以便注入。所以这就是为什么你应该避免在你的活动中创建演示者。它会将演示者与活动结合起来。