ant2009
ant2009

Reputation: 22486

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();
    }
}

Upvotes: 11

Views: 9244

Answers (3)

RexSplode
RexSplode

Reputation: 1505

Use when() and then() methods of your mocked context. It is actually described in example of official tutorial here.

@Mock
Context mMockContext;

@Test
public void readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    when(mMockContext.getString(R.string.hello_word))
            .thenReturn(FAKE_STRING);
    ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);

    // ...when the string is returned from the object under test...
    String result = myObjectUnderTest.getHelloWorldString();

    // ...then the result should be the expected one.
    assertThat(result, is(FAKE_STRING));

Upvotes: 6

Dat Nguyen
Dat Nguyen

Reputation: 1646

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.

Upvotes: 5

Dawood ibn Kareem
Dawood ibn Kareem

Reputation: 79807

It looks like you want to have a ParseNewsFeed object where the loadNewsFeed method has been stubbed, but other methods work correctly. The simplest way to get that would probably be to create a spy, something like

ParseNewsFeed spyParseNewsFeed = Mockito.spy(new ParseNewsFeed(context));
Mockito.doReturn("").when(spyParseNewsFeed).loadNewsFeed();

Upvotes: 3

Related Questions