is it possible to replace the MediaStore with a test double using robolectric?
Asked Answered
A

2

12

I have a class that queries the MediaStore for images. For example, I have code that looks like someContentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ... ). I want to test that, among other things, my queries to the MediaStore are correct.

What I've done so far in my test is this:

ContentResolver resolver = new Activity().getContentResolver();
ContentValues values = new ContentValues();
values.put( MediaStore.Images.Media.DATA,
            "/fake/path/file1.jpg" );
values.put( MediaStore.Images.Media.DATE_ADDED,
            fakeTime.getTime() );
resolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                 values );

I inject this resolver into my class, which performs a query on it. However, the query returns null. I saw this post: http://ikaruga2.wordpress.com/2013/07/29/roboelectric-and-contentresolverscontentproviders/ which says to register the content provider using ShadowContentResolver, so, something like:

ShadowContentResolver.registerProvider( MediaStore.AUTHORITY, <SOMETHING_GOES_HERE> );

but I don't know what to put for the content provider. Maybe use a MediaStore object? No, it is not a ContentProvider. Perhaps it is MediaProvider? The symbol can't be resolved, for some reason.

At this point I have serious doubts that this is even remotely the correct approach. Can someone steer me in the right direction?

using Robolectric 2.4 snapshot and API 19.

Antelope answered 5/7, 2014 at 22:34 Comment(2)
Have you found a solution yet?Dehorn
@SebastianRoth no, I have not.Antelope
T
4

You can rely on ShadowContentResolver.setCursor(Uri, BaseCursor) and RoboCursor to create test data and verify query. RoboCursor does not provide full implementation of a few methods, so you can either override and ignore them, or do something meaningful. RoboCursor extends BaseCursor, overriding setQuery() allows you to intercept queries and verify or do something there. Using Robolectric 3.0.

RoboCursor cursor = new RoboCursor() {
    @Override
    public void registerContentObserver(ContentObserver observer) {
        // no op
    }

    @Override
    public void unregisterContentObserver(ContentObserver observer) {
        // no op
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        // no op
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        // no op
    }

    @Override
    public boolean isClosed() {
        return true;
    }
};
cursor.setColumnNames(Arrays.asList(MediaStore.Images.Media._ID,
        MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
cursor.setResults(new Object[][]{
        new Object[]{1L, "WhatsApp"},
        new Object[]{2L, "Photos"},
        new Object[]{3L, "WhatsApp"}
});
shadowOf(ShadowApplication.getInstance().getContentResolver())
        .setCursor(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor);
Thickleaf answered 28/6, 2016 at 3:2 Comment(0)
P
0

I was having a similar problem with MediaStore, in my case a class tried to access the MediaStore through a managedQuery. When running a test that went through such code path, the test failed with a NPE, as the query returned null just like in your case.

The offending method

public String getPath(Uri uri, Activity activity) {
    String[] projection = { MediaColumns.DATA };
    Cursor cursor = activity
            .managedQuery(uri, projection, null, null, null);
    int column_index = cursor.getColumnIndexOrThrow(MediaColumns.DATA);
    cursor.moveToFirst();
    return cursor.getString(column_index);
}

Given that you are already injecting (I inject dependencies with Dagger on my project) the resolver, you might want to either mock it, if it suits you (via Mockito for example), or partially mock it, so the offending method defaults a usable return value.

PhotoUtilities pu=Mockito.spy(new PhotoUtilities());
Mockito.doReturn(Constants.getDataDir().getAbsolutePath()+"/aaa.txt").
when(pu).getPath(Mockito.isA(Uri.class), Mockito.isA(Activity.class));  

This way, when the test attempted to run getPath(), it would not run the query, but use the default return value set in the partial mock, allowing the test to go on.

I know it may not be what you're exactly looking for, but it might suit your needs as it did for me.

Parrott answered 17/12, 2014 at 19:9 Comment(1)
I actually do want to validate the query. Thanks for the response, though!Antelope

© 2022 - 2024 — McMap. All rights reserved.