如何使用Mockito模拟这个回调?

时间:2016-03-24 14:26:24

标签: unit-testing mockito

我在Presenter

中有这个生产代码
@UiThread
public void tryToReplaceLogo(String emailInitiallySearchedFor, String logoUrl) {
    if(isTheEmailWeAskedApiForStillTheSameAsInTheInputField(emailInitiallySearchedFor)){
        if (!TextUtils.isEmpty(logoUrl)) {
            downloadAndShowImage(logoUrl);
        } else {
            view.displayDefaultLogo();
        }
    }
}

public void downloadAndShowImage(String url) {

    final Target target = new Target() {

        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            view.displayLogoFromBitmap(bitmap);
        }

        @Override
        public void onBitmapFailed(Drawable errorDrawable) {

        }

        @Override
        public void onPrepareLoad(Drawable placeHolderDrawable) {

        }
    };

    Picasso.with(view.getViewContext()).load(url).resize(150, 150).centerInside().into(target);
}

这个单元测试它:

@Test
public void testDisplayLogoIfValidUrlReturnedAndEmailEnteredIsTheSame() throws Exception {
    when(loginView.getUserName()).thenReturn(VALID_EMAIL);
    when(loginView.getViewContext()).thenReturn(context);
    loginLogoFetcherPresenter.onValidateEmailEvent(createSuccessfulValidateEmailEvent(VALID_EMAIL));
    waitForAsyncTaskToKickIn();
    verify(loginView).displayLogoFromBitmap((Bitmap) anyObject());
}

但是,永远不会调用displayLogoFromBitmap方法,因此我的测试失败了。我需要模拟Target依赖项来调用onBitmapLoaded方法,但我不知道如何。

可能我需要创建一个实现Target的静态内部类,以便我可以在我的测试中设置一个Mocked实现,但是如何在模拟上调用onBitmapLoaded方法呢? / p>

编辑:

我现在在LoginPresenter中为Picasso设置了一个setter字段。在生产中,(因为我正在使用AndroidAnnotations),我在

中实例化它
@AfterInject
void initPicasso() {
    picasso = Picasso.with(context):
}

在我的测试中,我像这样嘲笑毕加索:

@Mock
Picasso picasso;

@Before
public void setUp() {
    picasso = mock(Picasso.class, RETURNS_DEEP_STUBS);
}

(我不记得为什么,但我现在不能使用Mockito 2.我认为这与某些东西不兼容)

在我的测试案例中,我达到了这一点,我不知道该怎么做:

@Test
public void displayLogoIfValidUrlReturnedAndEmailEnteredIsTheSame() throws Exception {
    when(loginView.getUserName()).thenReturn(VALID_EMAIL);
    when(loginView.getViewContext()).thenReturn(context);
    when(picasso.load(anyString()).resize(anyInt(), anyInt()).centerInside().into(???)) // What do I do here?
    loginLogoFetcherPresenter.onValidateEmailEvent(createSuccessfulValidateEmailEvent(VALID_EMAIL));
    waitForAsyncTaskToKickIn();
    verify(loginView).displayLogoFromBitmap((Bitmap) anyObject());
}

1 个答案:

答案 0 :(得分:3)

  

我需要模拟Target依赖

没有; 不要模拟被测系统。目标与任何事物一样,都是该系统的一部分;毕竟,你为它编写了代码。请记住,一旦你模拟了一个类,你就会提交不使用它的实现,所以试图模拟Target来调用onBitmapLoaded是没有意义的。

这里发生的事情是你将目标 - 这是你编写的值得测试的真实代码 - 传递给毕加索,这是你写的但不依赖的外部代码。这使得毕加索成为值得嘲笑的依赖者,但需要注意的是,如果他们改变了(例如方法变成最终的话),那么你无法控制的嘲弄界面会让你陷入困境。

所以:

  1. 模拟你的Picasso实例,并在加载时Picasso返回RequestCreator实例。 RequestCreator实现了Builder模式,因此它是Mockito 2.0的RETURNS_SELF选项或other Builder pattern strategies的主要候选者。
  2. Pass the Picasso instance into your system under test,而不是使用Picasso.with创建它。此时您可能不需要存根LoginView.getViewContext(),这是一件好事,因为您的测试可以与难以测试的Android系统类进行较少的交互,并且因为您进一步分离了对象创建(Picasso)来自商业逻辑。
  3. 在测试中使用ArgumentCaptor来提取RequestCreator.into上调用的Target方法。
  4. 如果您愿意,请在异步回调返回之前测试系统的状态。它是可选的,但它绝对是您的系统所处的状态,并且很容易忘记测试它。您可能会拨打verify(view, never()).onBitmapLoaded(any())
  5. 自己致电target.onBitmapLoaded。此时您已拥有target实例,并且从测试中明确调用您的代码(在您的测试系统中编写)应该感觉正确。
  6. 断言回调后状态,此处为verify(view).onBitmapLoaded(any())
  7. 请注意,现有的测试助手名为MockPicasso,但似乎需要Robolectric,我自己也没有检查过它的安全性或实用性。