简体   繁体   中英

Espresso Doesn't Match Drawable Inside RecyclerView

I'm trying to check whether the items within a RecyclerView contain the expected image drawables. I've tried to check for descendants but get an error to do with getting the id for the image view itself not the drawable I'm looking for.

onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image))
        .check(matches(atPosition(0, hasDescendant(withId(R.drawable.eap_financial)))));

Expected: has item at position 0: has descendant: with id: 2131165345
Got: "AppCompatImageView{id=2131296664, res-name=iv_service_image, desc=Resource 
type, visibility=VISIBLE, width=36...}

I've also tried to use a custom matcher but that's getting me even more frustrated as when I try to match the text against my textview it works fine but not when I try to match the imageview against my R.drawable id.

That's the guy I'm talking about:

public class RecyclerViewMatcher {
    private final int recyclerViewId;

    public RecyclerViewMatcher(int recyclerViewId) {
        this.recyclerViewId = recyclerViewId;
    }

    public Matcher<View> atPosition(final int position) {
        return atPositionOnView(position, -1);
    }

    public Matcher<View> atPositionOnView(final int position, final int targetViewId) {

        return new TypeSafeMatcher<View>() {
            View childView;

            public void describeTo(Description description) {

            }

            public boolean matchesSafely(View view) {

                if (childView == null) {
                    RecyclerView recyclerView = view.getRootView().findViewById(recyclerViewId);
                    if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
                        childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
                    } else {
                        return false;
                    }
                }

                if (targetViewId == -1) {
                    return view == childView;
                } else {
                    View targetView = childView.findViewById(targetViewId);
                    return view == targetView;
                }

            }
        };
    }
}

This piece of code works for textview

onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.tv_title)).check(matches(withText("Financial Assistance")));

But not for imageview:

onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image)).check(matches(withId(R.drawable.eap_financial));

Any help is much appreciated. Thanks very much.

Update

Adding the matcher I use to compare drawable when not a recyclerView item in case that could be adapter for this scenario as well?

public class EspressoTestsMatchers {

    public Matcher<View> withDrawable(final int resourceId) {
        return new DrawableMatcher(resourceId);
    }

    public Matcher<View> noDrawable() {
        return new DrawableMatcher(-1);
    }
}

public class DrawableMatcher extends TypeSafeMatcher<View> {
    private final int expectedId;

    public DrawableMatcher(int resourceId) {
        super(View.class);
        this.expectedId = resourceId;
    }

    @Override
    protected boolean matchesSafely(View target) {
        if (!(target instanceof ImageView)) {
            return false;
        }
        ImageView imageView = (ImageView) target;
        if (expectedId < 0) {
            return imageView.getDrawable() == null;
        }
        Resources resources = target.getContext().getResources();
        Drawable expectedDrawable = resources.getDrawable(expectedId);
        if (expectedDrawable == null) {
            return false;
        }
        Bitmap bitmap = getBitmap(imageView.getDrawable());
        Bitmap otherBitmap = getBitmap(expectedDrawable);
        return bitmap.sameAs(otherBitmap);
    }

    private Bitmap getBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    @Override
    public void describeTo(Description description) {

    }
}

I think it's unsafe if your matcher has to call setBounds on a View because the test result may change unexpectedly. You can try creating a custom matcher for ImageView based on HasBackgroundMatcher in Espresso:

public static Matcher<View> hasImage(int drawableId) {
    return new BoundedMatcher<View, ImageView>(ImageView.class) {

        @Override public void describeTo(Description description) {
            description.appendText("has image with drawable ID: " + drawableId);
        }

        @Override protected boolean matchesSafely(ImageView view) {
            return assertDrawable(view.getDrawable(), drawableId, view);
        }
    };
}

private static boolean compareBitmaps(Bitmap img1, Bitmap img2) {
    if (img1.getWidth() == img2.getWidth() && img1.getHeight() == img2.getHeight()) {
        int[] img1Pixels = new int[img1.getWidth() * img1.getHeight()];
        int[] img2Pixels = new int[img2.getWidth() * img2.getHeight()];

        img1.getPixels(img1Pixels, 0, img1.getWidth(), 0, 0, img1.getWidth(), img1.getHeight());
        img2.getPixels(img2Pixels, 0, img2.getWidth(), 0, 0, img2.getWidth(), img2.getHeight());

        return Arrays.equals(img1Pixels, img2Pixels);
    }
    return false;
}

private static boolean assertDrawable(Drawable actual, int expectedId, View v) {
    if (!(actual instanceof BitmapDrawable)) {
        return false;
    }

    Bitmap expectedBitmap = null;
    try {
        expectedBitmap = BitmapFactory.decodeResource(v.getContext().getResources(), expectedId);
        if (Build.VERSION.SDK_INT >= 12) {
            return ((BitmapDrawable) actual).getBitmap().sameAs(expectedBitmap);
        } else {
            return compareBitmaps(((BitmapDrawable) actual).getBitmap(), expectedBitmap);
        }
    } catch (OutOfMemoryError error) {
        return false;

    } finally {
        if (expectedBitmap != null) {
            expectedBitmap.recycle();
        }
    }
}

Then you can do:

onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image)).check(matches(hasImage(R.drawable.eap_financial));

I'm not sure if it works as you like but good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM