Issue
I have a preference util class to store and retrieve the data in shared preferences in a single place.
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);
}
}
Sample code showing PrefUtils usage in LoginActivity.java:
@Override public void showLoginView() {
Organization organization = PrefUtils.getOrganization(mActivity);
mOrganizationNameTextView.setText(organization.getName());
}
List of androidTestCompile
dependencies in build.gradle:
// 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());
}
}
The above code to return fakeOrganization
was not working, running the tests on login activity results in NullPointerException in line mOrganizationNameTextView.setText(organization.getName());
defined in the above LoginActivity.java class.
How to solve the above issue?
Solution
Approach-1:
Expose SharedPreference
with application scope using Dagger2 and use it like @Inject SharedPreferences mPreferences
in activity/fragment.
Sample code using the above approach to save(write) a custom preference:
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
To read a custom preference:
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
If you use it like above it results in breaking the DRY principle, since the code will be repeated in multiple places.
Approach-2:
This approach is based on the idea of having a separate preference class like StringPreference
/ BooleanPreference
which provides wrapper around the SharedPreferences code to save and retrieve values.
Read the below posts for detailed idea before proceeding with the solution:
- Persist your data elegantly: U2020 way by @tasomaniac
- Espresso 2.1: ActivityTestRule by chiuki
- Dagger 2 + Espresso 2 + Mockito
Code:
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();
}
}
Wherever you need the preference just inject it using Dagger @Inject OrganizationPreference mOrganizationPreference;
.
For androidTest
, I'm overriding the preference with a mock preference. Below is my configuration for android tests:
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());
}
}
Make sure you have dexmaker mockito added in build.gradle
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
Answered By - blizzard
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.