I took inspiration from google's sample on how to test your SharedPreferences
code here by creating a SharedPreferencesHelper
class:
You can see that the class uses actual strings hardcoded within the class as the keys to the sharedPreferences
- here's an extract of the class:
public class SharedPreferencesHelper {
// Keys for saving values in SharedPreferences.
static final String KEY_NAME = "key_name";
static final String KEY_DOB = "key_dob_millis";
static final String KEY_EMAIL = "key_email";
public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
// Start a SharedPreferences transaction.
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());
// Commit changes to SharedPreferences.
return editor.commit();
}
When testing this on using their SharedPreferencesHelperTest
class here, they access the mocked sharedPreferences
using the same variables defined in the above class:
An extract of that class is displayed below:
when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_NAME), anyString()))
.thenReturn(mSharedPreferenceEntry.getName());
when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_EMAIL), anyString()))
.thenReturn(mSharedPreferenceEntry.getEmail());
when(mMockSharedPreferences.getLong(eq(SharedPreferencesHelper.KEY_DOB), anyLong()))
.thenReturn(mSharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
The way google does this allowed them to bypass the issue of using a context
to pull a string out of the string.xml resource file and query it inside sharedPreferences
like how it is normally suppose to happen by using getString()
, ie:
mSharedPreferences.getString(context.getString(R.string.name),"");
However, I have read that it is not possible to use context.getString
as it is a final method and mockito cannot mock final methods:
Mockito - Overriding a method that takes primitive parameters
How can I then use mockito to unit test any methods with getString
? Any method with getString
will not work and my unit test will fail.
This is my class that I have written for SharedPreferences
and I would like to test it with the sharedPreferences
keys written with getStrings
:
public class SharedPreferencesHelper {
// The injected SharedPreferences implementation to use for persistence.
private final SharedPreferences mSharedPreferences;
private Context context;
public SharedPreferencesHelper(SharedPreferences sharedPreferences, Context context) {
mSharedPreferences = sharedPreferences;
this.context = context;
}
public boolean saveName(String name) {
// Start a SharedPreferences transaction.
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(context.getString(R.string.name), name);
return editor.commit();
}
public String fetchName() {
// Start a SharedPreferences transaction.
return mSharedPreferences.getString(context.getString(R.string.name),"");
}
public boolean saveGender(String gender) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(context.getString(R.string.gender), gender);
return editor.commit();
}
}
This is the test that I have written for the above class - it is very similar to google's SharedPreferencesHelperTest:
@RunWith(MockitoJUnitRunner.class)
public class SharedPreferencesHelperTest {
private static final String TEST_NAME = "Test name";
private SharedPreferencesHelper mMockSharedPreferencesHelper;
@Mock
SharedPreferences mMockSharedPreferences;
@Mock
SharedPreferences.Editor mMockEditor;
@Mock
MockContext context;
@Before
public void setUp() throws Exception {
// Create a mocked SharedPreferences.
mMockSharedPreferencesHelper = createMockSharedPreference();
}
@Test
public void testSaveName() throws Exception {
boolean success = mMockSharedPreferencesHelper.saveName(TEST_NAME);
Timber.e("success " + success);
assertThat("Checking that name was saved... returns true = " + success,
success, is(true));
String name = mMockSharedPreferencesHelper.fetchName();
Timber.e("name " + name);
assertThat("Checking that name has been persisted and read correctly " + name,
TEST_NAME,
is(name));
}
/**
* Creates a mocked SharedPreferences.
*/
private SharedPreferencesHelper createMockSharedPreference() {
// Mocking reading the SharedPreferences as if mMockSharedPreferences was previously written
// correctly.
when(mMockSharedPreferences.getString(Matchers.eq("name"), anyString()))
.thenReturn(TEST_NAME);
when(mMockSharedPreferences.getString(Matchers.eq("gender"), anyString()))
.thenReturn("M");
// Mocking a successful commit.
when(mMockEditor.commit()).thenReturn(true);
// Return the MockEditor when requesting it.
when(mMockSharedPreferences.edit()).thenReturn(mMockEditor);
return new SharedPreferencesHelper(mMockSharedPreferences, context);
}
}
Running the test fails as I have used getString
in my SharedPreferencesHelper
class. If I hard coded the keys, I will not get the error, i.e.:
public String fetchName() {
// Start a SharedPreferences transaction.
return mSharedPreferences.getString("name","");
}
One should not hard-code strings within the code so how do I solve this dilemma?