android studio 2.1. preview 4
I am creating a junit4 unit test
to test for opening a file contained in the raw directory.
However, everytime the code runs I can a null pointer from openRawResource
.
This is the function I am trying to test. This works when running on the actual device. But not in the unit test.
public String getNewsFeed(Context mContext) {
InputStream inputStream = mContext.getResources().openRawResource(R.raw.news_list); // Null pointer
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();
}
This is my test case
@RunWith(MockitoJUnitRunner.class)
public class NewsListPresenterTest {
@Mock
private Context mContext;
@Mock
private NewsListPresenter mNewsListPresenter;
@org.junit.Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mNewsListPresenter = new NewsListPresenter(mContext);
}
@org.junit.Test
public void testLoadNewsFeed() throws Exception {
assertNotNull(mNewsListPresenter.getNewsFeed(mContext));
}
}
Many thanks for any suggestions,
You are confusing the two types of unit tests in Android. This is not clear for many people so I'll explain it here.
Why it works on a device: Because this is an instrumented test. What is an instrumented test? A test that runs on a real device/emulator and the test code is in the 'src/androidTest' folder.
Why it doesn't work as a local junit test: Because local junit tests are not instrumented tests. Local Junit tests run on your computer's JVM, not on the device. Local Junit tests shouldn't contain/use Android code because the real Android code is on the device/emulator, not on your computer's JVM.
I suppose you want to make it run as a junit test to run it faster, and that's why I suppose you moved your test to the 'src/test' folder and context.getResources() is throwing a NullPointerException.
I think you have 2 options here:
For option 2, this is what I would do. Change the method's argument to an InputStream:
public String getNewsFeed(InputStream inputStream) {... use inputStream... }
Now your method doesn't see any Android code and you can test it as a normal junit method. You could then pass a fake inputStream to your method, like this:
@Test
public void testLoadNewsFeed() throws Exception {
String fileContents = "line one";
InputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
assertNotNull(mNewsListPresenter.getNewsFeed(inputStream));
}
If you still want to pass the same inputStream as you're using in your app (I wouldn't recommend it) you still can do it using this code in the test:
@Test
public void testLoadNewsFeed() throws Exception {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("raw/news_list");
assertNotNull(mNewsListPresenter.getNewsFeed(inputStream));
}
And you'll have to add this line to your build.gradle file:
sourceSets.test.resources.srcDirs += ["src/main"]
Android unit tests can be very confusing. You can read about these concepts in this blog post that I wrote
You have to tell the mContext mock what to do when getResources() is called on it.
when(mContext.getResources()).thenReturn(some_mock_of_Resources);
If you don't specify anything, the mock will return null.
For your example, this means you'll probably also need a Resources mock, and also tell it when to do/return when openRawResource() is called on that.
You have created a mock Context
so you have to stub the methods used by the method under test.
In getNewsFeed
your are using Context::getResources
so in your test before invocation of getNewsFeed
, you should have:
Resources resoures = ...
when(mContext.getResources()).thenReturn(resources);
The Mockito documentation of stubbing is pretty clear.
In your test, you have also some problems. I think that they have no consequences but they tend to show that you are discovering Mockito.
You wrote:
@Mock
private NewsListPresenter mNewsListPresenter;
Annotates the field with @Mock
, tells to mockito to create a mock of NewsListPresenter
which the class under test. (Not a big problem since your creating the real instance in the setUp
). You could have a look to @InjectMocks
(even if i am not a big fan of it).
Another point is that you use both @RunWith(MockitoJUnitRunner.class)
and
MockitoAnnotations.initMocks(this)
, you could avoid the last one since it is invoked by the runner. Furthermore, the runner is a better choice since it validate framework usage.
My last observation, is on your design. May be it is an android constraint, but you are injecting the context both in the presenter constructor and the getNewsFeed
method, this seems weird and could leads to inconsistant situation. I would choose the constructor injection (more object oriented design).
Another way to ease testing could be to extract the major parts of the method in an utility class and test the different branches (null stream, empty stream, valid stream, IOException...) without the need of mocking a context and resources:
class IOUtils {
static String readFromStream(InputStream inputStream) {
....
}
}
Note that guava provides a similar utility.
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