Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

junit4 opening a getResources().openRawResource using mockito runs nullpointer

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,

like image 954
ant2009 Avatar asked Mar 26 '16 18:03

ant2009


3 Answers

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:

  1. Use Robolectric to run this test as a junit test
  2. Refactor your method so it doesn't depend on Android's classes

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

like image 200
Yair Kukielka Avatar answered Nov 09 '22 16:11

Yair Kukielka


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.

like image 38
Doug Stevenson Avatar answered Nov 09 '22 17:11

Doug Stevenson


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.

like image 1
gontard Avatar answered Nov 09 '22 18:11

gontard