junit4 打开 getResources().openRawResource using mockito 运行 nu


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

非常感谢您的任何建议,

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

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

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

我想

你想让它作为一个junit测试运行得更快,这就是为什么我想你把你的测试移到'src/test'文件夹,context.getResources((抛出一个NullPointerException。

我认为您在这里有 2 个选择:

  1. 使用 Robolectric 将此测试作为 junit 测试运行
  2. 重构你的方法,使其不依赖于Android的类

对于选项 2,这就是我要做的。 将方法的参数更改为输入流:

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单元测试可能非常令人困惑。你可以在我写的这篇博文中读到这些概念

你必须告诉 mContext mock 当调用 getResources(( 时该怎么做。

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

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

对于您的示例,这意味着您可能还需要一个 Resources 模拟,并在调用 openRawResource(( 时告诉它何时执行/返回。

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

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

Mockito关于存根的文档非常清楚。


在你的测试中,你也有一些问题。我认为它们没有后果,但它们倾向于表明您正在发现Mockito。

你写道:

@Mock
private NewsListPresenter mNewsListPresenter;

@Mock注释字段,告诉 mockito 创建一个模拟NewsListPresenter被测试的类。(这不是一个大问题,因为您在setUp中创建真实实例(。你可以看看@InjectMocks(即使我不是它的忠实粉丝(。

另一点是你同时使用@RunWith(MockitoJUnitRunner.class)MockitoAnnotations.initMocks(this),你可以避免最后一个,因为它是由运行器调用的。此外,运行器是更好的选择,因为它验证了框架的使用情况。


我最后的观察是关于你的设计。可能是 android 约束,但您正在表示器构造函数和getNewsFeed方法中注入上下文,这似乎很奇怪,可能会导致不一致的情况。我会选择构造函数注入(更面向对象的设计(。

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

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

请注意,番石榴提供了类似的实用程序。

最新更新