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(); } }
3 Answers
Answers 1
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.
Answers 2
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();
Answers 3
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));
0 comments:
Post a Comment