junit4 使用 mockito 打开 getResources().openRawResource 运行 nullpointer
junit4 opening a getResources().openRawResource using mockito runs nullpointer
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));
}
}
非常感谢您的任何建议,
您必须告诉 mContext 模拟在调用 getResources() 时要做什么。
when(mContext.getResources()).thenReturn(some_mock_of_Resources);
如果您不指定任何内容,模拟将 return 为空。
对于您的示例,这意味着您可能还需要一个 Resources 模拟,并告诉它何时 do/return 在其上调用 openRawResource() 时。
您已经创建了一个模拟 Context
因此您必须对被测方法使用的方法进行存根。
在 getNewsFeed
中,您正在使用 Context::getResources
,因此在调用 getNewsFeed
之前的测试中,您应该:
Resources resoures = ...
when(mContext.getResources()).thenReturn(resources);
Mockito documentation of stubbing 说得很清楚了。
在你的测试中,你也遇到了一些问题。我认为它们没有任何后果,但它们往往表明您正在发现 Mockito。
您写道:
@Mock
private NewsListPresenter mNewsListPresenter;
用 @Mock
注释该字段,告诉 mockito 创建 NewsListPresenter
的模拟,其中 class 正在测试。 (这不是什么大问题,因为您在 setUp
中创建了真实实例)。你可以看看 @InjectMocks
(even if i am not a big fan of it).
另一点是你同时使用 @RunWith(MockitoJUnitRunner.class)
和
MockitoAnnotations.initMocks(this)
,你可以避免最后一个,因为它是由跑步者调用的。此外,跑步者是更好的选择,因为它 validate framework usage.
我最后的观察是关于您的设计。可能它是一个 android 约束,但是你在演示者构造函数和 getNewsFeed
方法中都注入了上下文,这看起来很奇怪并且可能导致不一致的情况。我会选择构造函数注入(更面向对象的设计)。
另一种简化测试的方法是在实用程序中提取方法的主要部分 class 并测试不同的分支(空流、空流、有效流、IOException...)需要模拟上下文和资源:
class IOUtils {
static String readFromStream(InputStream inputStream) {
....
}
}
请注意,guava 提供了一个 similar utility。
您混淆了 Android 中的两种类型的单元测试。这个很多人不是很清楚,我在这里解释一下。
为什么它可以在设备上运行:因为这是一个仪器测试。
什么是仪器化测试? 运行在真实device/emulator上的测试,测试代码在'src/androidTest'文件夹中。
为什么它不能用作本地 junit 测试:因为本地 junit 测试不是插桩测试。本地 Junit 测试 运行 在您计算机的 JVM 上,而不是在设备上。本地 Junit 测试不应 contain/use Android 代码,因为真正的 Android 代码在 device/emulator 上,而不是在您计算机的 JVM 上。
我想你想把它 运行 作为一个 junit 测试 运行 它更快,这就是为什么我想你把你的测试移到 'src/test' 文件夹和 context.getResources() 抛出 NullPointerException。
我想你有两个选择:
- 使用Robolectric到运行这个测试作为junit测试
- 重构您的方法,使其不依赖于 Android 的 类
对于选项 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 博客 post 中阅读这些概念
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));
}
}
非常感谢您的任何建议,
您必须告诉 mContext 模拟在调用 getResources() 时要做什么。
when(mContext.getResources()).thenReturn(some_mock_of_Resources);
如果您不指定任何内容,模拟将 return 为空。
对于您的示例,这意味着您可能还需要一个 Resources 模拟,并告诉它何时 do/return 在其上调用 openRawResource() 时。
您已经创建了一个模拟 Context
因此您必须对被测方法使用的方法进行存根。
在 getNewsFeed
中,您正在使用 Context::getResources
,因此在调用 getNewsFeed
之前的测试中,您应该:
Resources resoures = ...
when(mContext.getResources()).thenReturn(resources);
Mockito documentation of stubbing 说得很清楚了。
在你的测试中,你也遇到了一些问题。我认为它们没有任何后果,但它们往往表明您正在发现 Mockito。
您写道:
@Mock
private NewsListPresenter mNewsListPresenter;
用 @Mock
注释该字段,告诉 mockito 创建 NewsListPresenter
的模拟,其中 class 正在测试。 (这不是什么大问题,因为您在 setUp
中创建了真实实例)。你可以看看 @InjectMocks
(even if i am not a big fan of it).
另一点是你同时使用 @RunWith(MockitoJUnitRunner.class)
和
MockitoAnnotations.initMocks(this)
,你可以避免最后一个,因为它是由跑步者调用的。此外,跑步者是更好的选择,因为它 validate framework usage.
我最后的观察是关于您的设计。可能它是一个 android 约束,但是你在演示者构造函数和 getNewsFeed
方法中都注入了上下文,这看起来很奇怪并且可能导致不一致的情况。我会选择构造函数注入(更面向对象的设计)。
另一种简化测试的方法是在实用程序中提取方法的主要部分 class 并测试不同的分支(空流、空流、有效流、IOException...)需要模拟上下文和资源:
class IOUtils {
static String readFromStream(InputStream inputStream) {
....
}
}
请注意,guava 提供了一个 similar utility。
您混淆了 Android 中的两种类型的单元测试。这个很多人不是很清楚,我在这里解释一下。
为什么它可以在设备上运行:因为这是一个仪器测试。 什么是仪器化测试? 运行在真实device/emulator上的测试,测试代码在'src/androidTest'文件夹中。
为什么它不能用作本地 junit 测试:因为本地 junit 测试不是插桩测试。本地 Junit 测试 运行 在您计算机的 JVM 上,而不是在设备上。本地 Junit 测试不应 contain/use Android 代码,因为真正的 Android 代码在 device/emulator 上,而不是在您计算机的 JVM 上。
我想你想把它 运行 作为一个 junit 测试 运行 它更快,这就是为什么我想你把你的测试移到 'src/test' 文件夹和 context.getResources() 抛出 NullPointerException。
我想你有两个选择:
- 使用Robolectric到运行这个测试作为junit测试
- 重构您的方法,使其不依赖于 Android 的 类
对于选项 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 博客 post 中阅读这些概念