Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using mockito to test function that uses a context

Android Studio 2.1.2

I am trying to test getJsonFromResource which calls loadNewsFeed. I want to be able to test 2 cases 1 where loadNewsFeed will return an empty string and the other where it will return some json string.

So I am trying to mock the loadNewsFeed function to return an empty string. However, when the concrete getJsonFromResource is called it will call the real loadNewsFeed and cause a null pointer exception. This is what I have tried in my test comments explaining what I am doing:

@Test
public void shouldFailIfJSONStringIsEmpty() throws Exception {
    /* Mock Context class */
    Context context = mock(Context.class);
    /* initialize the concrete parseNewsFeed passing in the fake context */
    ParseNewsFeed parseNewsFeed = new ParseNewsFeed(context);
    /* Create a mock of the parseNewsFeed so a fake call to loadNewsFeed will return an empty string */
    ParseNewsFeed mockParseNewsFeed = mock(ParseNewsFeed.class);
    /* Mock the events that will be verified */
    ParseNewsFeedContract.Events<Status> mockEvents = mock(ParseNewsFeedContract.Events.class);

    /* Return an empty string when loadNewsFeed is called */
    when(mockParseNewsFeed.loadNewsFeed()).thenReturn("");

    /* Called the concrete getJsonFromResource */
    parseNewsFeed.getJsonFromResource(mockEvents);

    /* verify that onNewsFailure was called once and onNewsSuccess was never called */
    verify(mockEvents, times(1)).onNewsFailure(anyString());
    verify(mockEvents, never()).onNewsSuccess(any(Status.class));
}

This is the class I am trying to test.

public class ParseNewsFeed implements ParseNewsFeedContract {
    private Context mContext;

    public ParseNewsFeed(Context context) {
        if(context != null) {
            Timber.d("mContext != null");
            mContext = context;
        }
    }

    /**
     * Get the json from the local resource file and add to the cache to save loading each time
     * @return the json in string representation
     */
    @Override
    public void getJsonFromResource(Events<Status> events) {
        /* Get the json in string format */
        final String jsonString = loadNewsFeed();

        /* Check that is contains something */
        if(!jsonString.isEmpty()) {
            try {
                final Status status = new Gson().fromJson(jsonString, Status.class);
                if(status != null) {
                    Timber.d("url: %s", status.getResults().get(0).getMultimedia().get(0).getUrl());
                    events.onNewsSuccess(status);
                }
                else {
                    Timber.e("status == null");
                    events.onNewsFailure("Failed to get results from json");
                }
            }
            catch (JsonSyntaxException e) {
                Timber.e("Invalid JSON: %s", e.getMessage());
                events.onNewsFailure(e.getMessage());
            }
        }
    }

    /**
     * Opens and reads from the news_list and writes to a buffer
     * @return return the json representation as a string or a empty string for failure
     */
    public String loadNewsFeed() {
        InputStream inputStream = mContext.getResources().openRawResource(R.raw.news_list);
        Writer writer = new StringWriter();
        char[] buffer = new char[1024];

        try {
            InputStreamReader inputReader = new InputStreamReader(inputStream, "UTF-8");
            BufferedReader bufferReader = new BufferedReader(inputReader);
            int n;
            while ((n = bufferReader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }

            inputStream.close();
        }
        catch (IOException ioException) {
            return "";
        }

        return writer.toString();
    }
}
like image 328
ant2009 Avatar asked Aug 03 '16 03:08

ant2009


1 Answers

First of all, the reason why your original code doesn't work is because there's no relationship between your two objects parseNewsFeed and mockParseNewsFeed, hence the stubbing that you do for the mockParseNewsFeed doesn't have any effect when you invoke parseNewsFeed.getJsonFromResource(mockEvents). Using spy as David Wallace suggested would work, but if I were you, I would rewrite the code a bit differently to make it even easier to test.

One observation is that the code in loadNewsFeed() method doesn't seem to have a strong relationship with the ParseNewsFeed class, so I'd extract this code into an object (e.g. NewsFeedLoader), and then have this object as a dependency of ParseNewsFeed class. Then you can mock this Loader easily (return "" or any string that you want when passing a Context and possibly the R.raw.news_list id as well). With this Loader class, you can even unit test it separately from the ParseNewsFeed, and being able to improve the Loader however you want to (e.g. a better way to read a raw resource) without affecting the ParseNewsFeed class.

like image 51
Dat Nguyen Avatar answered Nov 03 '22 21:11

Dat Nguyen