junit4打开一个getResources()。使用mockito的openRawResource运行nullpointer

时间:2016-03-26 18:16:38

标签: android mockito junit4

android studio 2.1. preview 4

我正在创建一个junit4 unit test来测试打开原始目录中包含的文件。

但是,每次代码运行时,我都可以从openRawResource开始指向空白。

这是我试图测试的功能。这在实际设备上运行时有效。但不是在单元测试中。

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();
}

这是我的测试用例

@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));
    }
}

非常感谢任何建议,

3 个答案:

答案 0 :(得分:7)

您在Android中混淆了两种类型的单元测试。这对许多人来说并不清楚,所以我将在这里解释一下。

为什么它可以在设备上运行:因为这是一个检测测试。 什么是仪器化测试?在真实设备/模拟器上运行的测试,测试代码位于'src / androidTest'文件夹中。

为什么它不能用作本地junit测试:因为本地junit测试不是测试测试。本地Junit测试在计算机的JVM上运行,而不是在设备上运行。本地Junit测试不应包含/使用Android代码,因为真正的Android代码位于设备/模拟器上,而不是计算机的JVM上。

我想你想让它作为junit测试运行以更快地运行它,这就是为什么我认为你将测试移动到'src / test'文件夹而context.getResources()抛出了NullPointerException。

我认为你有两个选择:

  1. 使用Robolectric将此测试作为junit test
  2. 运行
  3. 重构您的方法,使其不依赖于Android的类
  4. 对于选项2,这就是我要做的。将方法的参数更改为InputStream:

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

    现在您的方法没有看到任何Android代码,您可以将其作为普通的junit方法进行测试。然后,您可以将假冒的inputStream传递给您的方法,如下所示:

    @Test
    public void testLoadNewsFeed() throws Exception {
        String fileContents = "line one";
        InputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
        assertNotNull(mNewsListPresenter.getNewsFeed(inputStream));
    }
    

    如果您仍想传递与在应用中使用相同的inputStream(我不推荐它),您仍然可以在测试中使用此代码执行此操作:

    @Test
        public void testLoadNewsFeed() throws Exception {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("raw/news_list");
            assertNotNull(mNewsListPresenter.getNewsFeed(inputStream));
        }
    

    您必须将此行添加到build.gradle文件中:

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

    Android单元测试可能非常混乱。您可以在我撰写的this博文中了解这些概念

答案 1 :(得分:6)

你必须告诉mContext模拟在调用getResources()时要做什么。

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

如果您没有指定任何内容,则模拟将返回null。

对于您的示例,这意味着您可能还需要一个Resources mock,并且还告诉它何时执行/返回openRawResource()的时候。

答案 2 :(得分:2)

您已经创建了一个模拟Context,因此您必须存根被测试方法使用的方法。 在getNewsFeed您正在使用Context::getResources,因此在调用getNewsFeed之前的测试中,您应该:{/ p>

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

Mockito documentation of stubbing非常明确。

在你的测试中,你也遇到了一些问题。我认为它们没有任何后果,但它们往往表明你正在发现Mockito。

您写道:

@Mock
private NewsListPresenter mNewsListPresenter;

使用@Mock注释该字段,告诉mockito创建一个NewsListPresenter的模拟,该模拟正在测试中。 (因为您在setUp中创建了真实实例,因此不是一个大问题)。您可以查看@InjectMocks(即使i am not a big fan of it)。

另一点是你同时使用@RunWith(MockitoJUnitRunner.class)和。{ MockitoAnnotations.initMocks(this),你可以避免最后一个,因为它是由跑步者调用的。此外,跑步者是一个更好的选择,因为validate framework usage

我的最后一个观察是你的设计。可能是一个android约束,但是你在presenter构造函数和getNewsFeed方法中注入了上下文,这看起来很奇怪并且可能导致不一致的情况。我会选择构造函数注入(更面向对象的设计)。

另一种简化测试的方法是在实用程序类中提取方法的主要部分并测试不同的分支(空流,空流,有效流,IOException ......),而无需模拟上下文和资源:

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

请注意,番石榴提供similar utility