Reputation: 14711
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
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:
RETURNS_SELF
option or other Builder pattern strategies.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.RequestCreator.into
.verify(view, never()).onBitmapLoaded(any())
.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.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