Kaloyan Roussev
Kaloyan Roussev

Reputation: 14711

How to mock this callback using Mockito?

I have this production code in my 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);
}

And this unit test for it:

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

However, the displayLogoFromBitmap method is never called so my test fails. I need to mock the Target dependency to invoke the onBitmapLoaded method but I don't know how.

Possibly I need to create a static inner class that implements Target so that I can set a Mocked implementation of that in my tests, but how do I invoke the onBitmapLoaded method on the mock?

EDIT:

I have a setter field for Picasso in my LoginPresenter now. In production, (as I am using AndroidAnnotations), I instantiate it in

@AfterInject
void initPicasso() {
    picasso = Picasso.with(context):
}

In my test, I mock Picasso like so:

@Mock
Picasso picasso;

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

(I don't remember why, but I can't use Mockito 2 at this point. It was some incompatibility with something, I think)

In my test case, I got to this point and I don't know what to do:

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

Upvotes: 1

Views: 896

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95614

I need to mock the Target dependency

No; do not mock the system under test. Target is as much a part of that system as anything; you wrote the code for it, after all. Remember, once you mock out a class, you commit to not using its implementation, so trying to mock Target to invoke onBitmapLoaded is missing the point.

What's going on here is that you're passing Target—which is real code you wrote that is worth testing—into Picasso, which is external code you didn't write but do depend on. This makes Picasso the dependency worth mocking, with the caveat that mocking interfaces you don't control can get you into trouble if they change (e.g. a method turns final).

So:

  1. Mock your Picasso instance, and the RequestCreator instance Picasso returns when it loads. RequestCreator implements the Builder pattern, so it's a prime candidate for Mockito 2.0's RETURNS_SELF option or other Builder pattern strategies.
  2. Pass the Picasso instance into your system under test, rather than creating it using Picasso.with. At this point you may not need to stub LoginView.getViewContext(), which is a good thing as your test can interact less with hard-to-test Android system classes, and because you've further separated object creation (Picasso) from business logic.
  3. Use an ArgumentCaptor in your test to extract out the Target method that was called on RequestCreator.into.
  4. Test the state of the system before the async callback returns, if you'd like. It's optional, but it's definitely a state your system will be in, and it's easy to forget to test it. You'd probably call verify(view, never()).onBitmapLoaded(any()).
  5. Call target.onBitmapLoaded yourself. You have the target instance at this point, and it should feel correct to explicitly call your code (that is written in your system-under-test) from your test.
  6. Assert your after-callback state, which here would be verify(view).onBitmapLoaded(any()).

Note that there is an existing test helper called MockPicasso, but it seems to require Robolectric, and I haven't reviewed its safety or utility myself.

Upvotes: 3

Related Questions