Junit4 opens getResources (). openRawResource, using mockito, starts nullpointer

android studio 2.1. preview 4 

I am creating a junit4 unit test to verify the opening of the file contained in the source directory.

However, every time the code is executed, I can specify a null pointer from openRawResource .

This is the function I'm trying to test. This works when working on the device itself. But not in 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)); } } 

Thanks so much for any suggestions,

+5
source share
3 answers

You are misleading two types of unit tests in Android. This is not clear to many people, so I will explain it here.

Why does this work on the device . Because it is a verification test. What is an instrument test? A test that runs on a real device / emulator and test code is located in the "src / androidTest" folder.

Why this does not work as a local junit test . Since local junit tests are not instrumental tests. Junit local tests run on your JVM, not the device. Local Junit tests should not contain / use Android code, because the real Android code is on the device / emulator, and not on your JVM computer.

I assume that you want to run it as a junit test to run it quickly, and therefore I assume that you moved the test to the src / test folder, and context.getResources () throws a NullPointerException.

I think you have 2 options:

  • Use Robolectric to run this test as a junit test
  • Refactoring your method so that it does not depend on Android classes.

For option 2, this is what I would do. Change the method argument to InputStream:

 public String getNewsFeed(InputStream inputStream) {... use inputStream... } 

Now your method does not see any Android code, and you can test it as a regular junit method. Then you can pass the fake input stream to your method, for example:

 @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 input stream that you use in your application (I would not recommend it), you can still do this 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 will have to add this line to the build.gradle file:

 sourceSets.test.resources.srcDirs += ["src/main"] 

Android device tests can be very confusing. You can read about these concepts in this blog post I wrote.

+5
source

You should tell the mContext layout what to do when getResources () is called on it.

 when(mContext.getResources()).thenReturn(some_mock_of_Resources); 

If you do not specify anything, mock will return null.

In your example, this means that you will probably also need a resource layout, and also tell you when to do / return when openRawResource () is called on it.

+6
source

You created a mock Context , so you need to drown out the methods used by the test method. In getNewsFeed you use Context::getResources , so in your test, before calling getNewsFeed you should have:

 Resources resoures = ... when(mContext.getResources()).thenReturn(resources); 

Mockito's documentation on stubbing is pretty straightforward .


In your test you also have some problems. I think that they have no consequences, but they usually show that you open Mockito.

You wrote:

 @Mock private NewsListPresenter mNewsListPresenter; 

Designates a field with @Mock , tells mockito to create a NewsListPresenter layout that is being tested by the class. (Not a big problem since creating the real instance in setUp ). You could take a look at @InjectMocks (even if I'm not a big fan of this ).

Another thing is that you use both @RunWith(MockitoJUnitRunner.class) and MockitoAnnotations.initMocks(this) , you can avoid the latter, as it is called by the runner. In addition, a runner is the best choice, because check the use of the framework .


My last observation is your design. This may be an android limitation, but you are inserting a context both in the presenter constructor and in the getNewsFeed method, this seems strange and can lead to an inconsistent situation. I would choose constructor injection (more object oriented design).

Another way to simplify testing can be to extract the main parts of the method in the utility class and test various branches (zero stream, empty stream, actual stream, IOException ...) without having to mock the context and resources:

 class IOUtils { static String readFromStream(InputStream inputStream) { .... } } 

Note that guava provides a similar utility .

+2
source

Source: https://habr.com/ru/post/1245824/


All Articles