ant2009 ant2009 - 24 days ago 7
Android Question

Unit test Glide: make sure ImageView has correct image

Android Studio 3.0 Beta 5
robolectric:3.3.1


I have a the following view holder that loads a image url using the glide library. I am trying to find a way to unit test this:

public class MovieActorsViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.civActorPicture) CircleImageView actorPicture;
@BindView(R.id.tvName) TextView name;
@BindView(R.id.tvCharacter) TextView character;

private Context context;

public MovieActorsViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);

context = itemView.getContext();
}

public void populateActor(Actor actor) {
Glide.with(context)
.load(actor.getPicturePath())
.placeholder(R.drawable.people_placeholder)
.into(actorPicture);

name.setText(actor.getName());
character.setText(actor.getCharacter());
}
}


This is the unit test I have done, but I am not sure how can I unit test the image view. I am not sure using Mockito to mock the Glide library would work?

@RunWith(RobolectricTestRunner.class)
public class MovieActorsViewHolderTest {
private MovieActorsViewHolder movieActorsViewHolder;

@Before
public void setup() {
final Context context = ShadowApplication.getInstance().getApplicationContext();
final View view = LayoutInflater.from(context).inflate(R.layout.movie_actors_item, new LinearLayout(context));

movieActorsViewHolder = new MovieActorsViewHolder(view);
}

@Test
public void testShouldPopulateActorWithValidData() {
final Actor actor = getActor();
movieActorsViewHolder.populateActor(actor);

/* test that the image view */
final ShadowDrawable shadowDrawable = Shadows.shadowOf(movieActorsViewHolder.actorPicture.getDrawable());
final Drawable drawable = Drawable.createFromPath(actor.getPicturePath());
assertThat(drawable, is(shadowDrawable.getCreatedFromResId()));

assertThat(movieActorsViewHolder.name.getText(), is(actor.getName()));
assertThat(movieActorsViewHolder.character.getText(), is(actor.getCharacter()));
}

private Actor getActor() {
return new Actor(
"https://image.tmdb.org/t/p/w92/dRLSoufWtc16F5fliK4ECIVs56p.jpg",
"Robert Danny Junior",
"Iron Man");
}


}

Output:

Expected: is <org.robolectric.shadows.ShadowBitmapDrawable@ffffffe0>
but: was <android.graphics.drawable.BitmapDrawable@ffffffe0>
Expected :is <org.robolectric.shadows.ShadowBitmapDrawable@ffffffe0>

Actual :<android.graphics.drawable.BitmapDrawable@ffffffe0>


Many thanks for any suggestions.

Answer Source

but I am not sure how can I unit test the image view

I think you are on the wrong way: you want to test whether Glide is working as expected. That's not your responsibility as a client of that library. Glide has its own tests that verify it works as expected, you should only unit test the logics that you implement within your app.

Nevertheless, if you still want to do something similar, than you have to introduce some separation in your ViewHolder: a component that is responsible for loading image into ImageView.


    public interface ImageLoader {

        void load(Context context,
                  String path,
                  @DrawableRes int placeholder,
                  ImageView imageView);
    }

Whose implementation is following class:


    public class ImageLoaderImpl implements ImageLoader {

        @Override
        public void load(Context context, String path, int placeholder, ImageView imageView) {
            Glide.with(context)
                    .load(path)
                    .placeholder(placeholder)
                    .into(imageView);
        }

    }

Now your ViewHolder will become something like this:


    class MovieActorsViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.picture)
        ImageView imageView;
        // other views

        ImageLoader imageLoader;

        MovieActorsViewHolder(View itemView, ImageLoader imageLoader) {
            super(itemView);
            ButterKnife.bind(this, itemView);

            this.imageLoader = imageLoader;
        }

        void populateActor(Actor actor) {
            imageLoader.load(itemView.getContext(),
                    actor.getPicturePath(),
                    R.drawable.people_placeholder,
                    imageView);

            // other actions                
        }

    }

This will give you flexibility to mock ImageLoader class.

Now to the test. Here's the setup:


    @Before
    public void setup() {
        imageLoader = Mockito.mock(ImageLoader.class);

        activity = Robolectric.setupActivity(MainActivity.class);
        ViewGroup root = (ViewGroup) activity.findViewById(R.id.root);

        View inflated = activity.getLayoutInflater().inflate(R.layout.item, root);
        holder = new MovieActorsViewHolder(inflated, imageLoader);
    }

And here's test method:


    @Test
    public void test() throws InterruptedException {
        final String path = "https://image.tmdb.org/t/p/w92/dRLSoufWtc16F5fliK4ECIVs56p.jpg";
        final Actor actor = new Actor(path);
        final Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
        final BitmapDrawable drawable = new BitmapDrawable(activity.getResources(), bitmap);

        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                holder.imageView.setImageDrawable(drawable);
                return null;
            }
        }).when(imageLoader).load(activity, path, R.drawable.people_placeholder, holder.imageView);

        holder.populateActor(actor);

        assertEquals(holder.imageView.getDrawable(), drawable);
    }

This will pass. But ask yourself: what have you tested with this? Instead, a better test would be to make sure, that imageLoader.load(...) has been called with correct params, ignore logics of how Glide will download that image into ImageView.


I wasn't trying to test the Glide API, but only to test that the image was successfully loaded into the imageview, or maybe just make sure the glide was called with the correct parameters.

Those two statements are basically the same thing: if you verify that you delegate the job to Glide with correct parameters, than it verifies that Glide will load the image correctly.

Now, the question boils down to how to verify that you are delegating the job to Glide with correct parameters?

In the above mentioned scenario:


    holder.populateActor(actor);

    verify(imageLoader).load(activity, path, R.drawable.people_placeholder, holder.imageView);

This will check, whether imageLoader was queried with those parameters.

just trying to find the best way to ensure the image view has an image

What you want is to create an abstraction, that would fill in the ImageView with some mock Drawable, and within your test you would check whether the ImageView has actually been filled with that drawable. Isn't this exactly the same, that you verify that the method on your abstraction was called (in above mentioned case ImageLoader#load())? So, there is no need to explicitly check whether the ImageView was filled in with the Drawable, because it sure will, as long as you have mocked that component also.

I guess this would mean mocking the Glide

Do not depend on implementation, depend on abstraction. What if you later decide to move from Glide to SomeAwesomeImageLoder? You have to change everything within your sources as well as tests.

On the other hand, if you have a class that is responsible for image loading, you'd encapsulate the loading logics within that class only, thus only this class would need a change. Also, this gives a perfect seam to perform unit testing.