我有一个首选项util类,可以在一个地方存储和检索共享首选项中的数据。
Prefutils.java:
public class PrefUtils {
private static final String PREF_ORGANIZATION = "organization";
private static SharedPreferences getPrefs(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
private static SharedPreferences.Editor getEditor(Context context) {
return getPrefs(context).edit();
}
public static void storeOrganization(@NonNull Context context,
@NonNull Organization organization) {
String json = new Gson().toJson(organization);
getEditor(context).putString(PREF_ORGANIZATION, json).apply();
}
@Nullable public static Organization getOrganization(@NonNull Context context) {
String json = getPrefs(context).getString(PREF_ORGANIZATION, null);
return new Gson().fromJson(json, Organization.class);
}
}
示例代码显示 LoginActivity.java中的PrefUtils用法:
@Override public void showLoginView() {
Organization organization = PrefUtils.getOrganization(mActivity);
mOrganizationNameTextView.setText(organization.getName());
}
build.gradle中的androidTestCompile
个依赖关系列表:
// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-contrib:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-intents:$project.ext.espressoVersion"
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
的src / androidTest /../ LoginScreenTest.java
@RunWith(AndroidJUnit4.class) @LargeTest public class LoginScreenTest {
@Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class);
@Before public void setUp() throws Exception {
when(PrefUtils.getOrganization(any()))
.thenReturn(HelperUtils.getFakeOrganization());
}
}
上面的返回fakeOrganization
的代码无效,在登录活动上运行测试会导致在上面的LoginActivity.java类中定义的行mOrganizationNameTextView.setText(organization.getName());
中出现NullPointerException。
如何解决上述问题?
答案 0 :(得分:3)
<强>方法-1:强>
使用Dagger2将SharedPreference
与应用范围公开,并在活动/片段中使用@Inject SharedPreferences mPreferences
。
使用上述方法保存(写入)自定义首选项的示例代码:
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
要阅读自定义偏好设置:
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
如果你像上面一样使用它会导致破坏DRY原则,因为代码将在多个地方重复。
<强>方法-2:强>
这种方法基于这样的想法,即拥有一个单独的首选项类,如StringPreference
/ BooleanPreference
,它提供了包含SharedPreferences代码的包装器来保存和检索值。
在继续解决方案之前,请阅读以下帖子以获取详细信息:
<强>代码:强>
<强> ApplicationModule.java 强>
@Module public class ApplicationModule {
private final MyApplication mApplication;
public ApplicationModule(MyApplication application) {
mApplication = application;
}
@Provides @Singleton public Application provideApplication() {
return mApplication;
}
}
<强> DataModule.java 强>
@Module(includes = ApplicationModule.class) public class DataModule {
@Provides @Singleton public SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
}
<强> GsonModule.java 强>
@Module public class GsonModule {
@Provides @Singleton public Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
}
<强> ApplicationComponent.java 强>
@Singleton @Component(
modules = {
ApplicationModule.class, DataModule.class, GsonModule.class
}) public interface ApplicationComponent {
Application getMyApplication();
SharedPreferences getSharedPreferences();
Gson getGson();
}
<强> MyApplication.java 强>
public class MyApplication extends Application {
@Override public void onCreate() {
initializeInjector();
}
protected void initializeInjector() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
<强> OrganizationPreference.java 强>
public class OrganizationPreference {
public static final String PREF_ORGANIZATION = "pref_organization";
SharedPreferences mPreferences;
Gson mGson;
@Inject public OrganizationPreference(SharedPreferences preferences, Gson gson) {
mPreferences = preferences;
mGson = gson;
}
@Nullable public Organization getOrganization() {
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
return null;
}
public void saveOrganization(Organization organization) {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
}
}
只要您需要偏好,只需使用Dagger @Inject OrganizationPreference mOrganizationPreference;
注入它。
对于androidTest
,我使用模拟首选项覆盖首选项。下面是我对android测试的配置:
<强> TestDataModule.java 强>
public class TestDataModule extends DataModule {
@Override public SharedPreferences provideSharedPreferences(Application app) {
return Mockito.mock(SharedPreferences.class);
}
}
<强> MockApplication.java 强>
public class MockApplication extends MyApplication {
@Override protected void initializeInjector() {
mApplicationComponent = DaggerTestApplicationComponent.builder()
.applicationModule(new TestApplicationModule(this))
.dataModule(new TestDataModule())
.build();
}
}
<强> LoginScreenTest.java 强>
@RunWith(AndroidJUnit4.class) public class LoginScreenTest {
@Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class, true, false);
@Inject SharedPreferences mSharedPreferences;
@Inject Gson mGson;
@Before public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
MyApplication app = (MyApplication) instrumentation.getTargetContext().getApplicationContext();
TestApplicationComponent component = (TestApplicationComponent) app.getAppComponent();
component.inject(this);
when(mSharedPreferences.getString(eq(OrganizationPreference.PREF_ORGANIZATION),
anyString())).thenReturn(mGson.toJson(HelperUtils.getFakeOrganization()));
mActivityTestRule.launchActivity(new Intent());
}
}
确保在 build.gradle
中添加了dexmaker mockitoandroidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
答案 1 :(得分:1)
不幸的是,Mockito无法独立完成你想要的任务。您有两个选择,一个是使用Power Mock,另一个是将Prefutils更改为普通类,而是使用依赖注入框架。
Power Mock
简单明了,这将让你模拟静态方法check out this SO post for details。在不利方面,它可能会导致其他问题基于SO帖子中的评论。
依赖注入方法(我的原始答案)
您正在尝试使用应用程序的某些行为编写 UI测试&#34;模拟&#34;。 Mockito旨在让您编写单元测试,您可以在其中测试特定对象(或对象组)并模拟其中的一些行为。
您可以看到有关如何在这些测试中使用mockito的一些示例(1,2)。他们都没有测试UI,而是实例化一个对象&#34; stub&#34; /&#34; mock&#34;一些如果它的行为,然后测试其余的。
要实现您的目标,您需要一个依赖注入框架。这允许您更改&#34;实施&#34;根据您运行的是实际应用程序还是测试,您的某些应用程序。
模拟类/对象行为的细节因框架而异。这个blog post讨论了如何将Dagger 2与Mockito和espresso一起使用,您可以为测试应用相同的方法。它还提供了演示文稿的链接,提供了更多关于匕首2的背景信息。
如果您不喜欢匕首2,那么您还可以结帐RoboGuice和Dagger。请注意,我不认为黄油刀会满足您的需求,因为它不支持注射Pojos。