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();
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With