41

I have just read about Unit Instrumented Testing in Android and I wonder how I can mock a SharedPreferences without any SharedPreferencesHelper class on it like here

My code is:

public class Auth {
private static SharedPreferences loggedUserData = null;
public static String getValidToken(Context context)
{
    initLoggedUserPreferences(context);
    String token = loggedUserData.getString(Constants.USER_TOKEN,null);
    return token;
}
public static String getLoggedUser(Context context)
{
    initLoggedUserPreferences(context);
    String user = loggedUserData.getString(Constants.LOGGED_USERNAME,null);
    return user;
}
public static void setUserCredentials(Context context, String username, String token)
{
    initLoggedUserPreferences(context);
    loggedUserData.edit().putString(Constants.LOGGED_USERNAME, username).commit();
    loggedUserData.edit().putString(Constants.USER_TOKEN,token).commit();
}

public static HashMap<String, String> setHeaders(String username, String password)
{
    HashMap<String, String> headers = new HashMap<String, String>();
    String auth = username + ":" + password;
    String encoding = Base64.encodeToString(auth.getBytes(), Base64.DEFAULT);
    headers.put("Authorization", "Basic " + encoding);
    return headers;
}

public static void deleteToken(Context context)
{
    initLoggedUserPreferences(context);
    loggedUserData.edit().remove(Constants.LOGGED_USERNAME).commit();
    loggedUserData.edit().remove(Constants.USER_TOKEN).commit();
}

public static HashMap<String, String> setHeadersWithToken(String token) {
    HashMap<String, String> headers = new HashMap<String, String>();
    headers.put("Authorization","Token "+token);
    return headers;
}
private static SharedPreferences initLoggedUserPreferences(Context context)
{
    if(loggedUserData == null)
        loggedUserData = context.getSharedPreferences(Constants.LOGGED_USER_PREFERENCES,0);
    return loggedUserData;
}}

Is is possible to mock SharedPreferences without creating other class on it?

adamura88
  • 513
  • 1
  • 4
  • 13

3 Answers3

78

So, because SharedPreferences comes from your context, it's easy:

final SharedPreferences sharedPrefs = Mockito.mock(SharedPreferences.class);
final Context context = Mockito.mock(Context.class);
Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);

// no use context

for example, for getValidToken(Context context), the test could be:

@Before
public void before() throws Exception {
    this.sharedPrefs = Mockito.mock(SharedPreferences.class);
    this.context = Mockito.mock(Context.class);
    Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs);
}

@Test
public void testGetValidToken() throws Exception {
    Mockito.when(sharedPrefs.getString(anyString(), anyString())).thenReturn("foobar");
    assertEquals("foobar", Auth.getValidToken(context));
    // maybe add some verify();
}
Mr-IDE
  • 7,051
  • 1
  • 53
  • 59
  • 2
    Ok, thank you. How about `public static void setUserCredentials(Context context, String username, String token)` test? I would like to set user credentials before `testGetValidToken()` I suppose that I need mock for `SharedPreferences.Editor` but if I save some key by that mock it will be available by `SharedPreferences` mock? – adamura88 Jan 31 '16 at 15:28
  • 2
    I would return an `Editor` mock when `edit` is called. "will be available by SharedPreferences mock?" no, a mock is a dumb object –  Jan 31 '16 at 17:52
  • @ArunavSanyal you suggested "edit" adds some comment, I don't see how that qualify as syntax error: http://i64.tinypic.com/24curep.png –  Jul 26 '16 at 17:57
  • 1
    Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPreferences); – Arunav Sanyal Jul 26 '16 at 17:58
  • I added the comment because edits less than six chars are not supported. – Arunav Sanyal Jul 26 '16 at 18:01
  • OK, sorry I didn't see the ")" :/ –  Jul 26 '16 at 18:14
  • @RC. I'm following same & using context object to create FirebaseAnalytics singleton object. But I'm getting **_"FirebaseAnalytics.getInstance(Unknown Source)"_** issue with **_java.lang.NullPointerException: null reference_**. Any suggestion !! – CoDe Dec 08 '17 at 11:55
  • yes I suggest you [ask a new question](https://stackoverflow.com/questions/ask) with your code an a stacktrace of your NPE –  Dec 08 '17 at 12:10
11

The following example shows how you might create a unit test that uses a mock Context object such as shared preference.

@RunWith(MockitoJUnitRunner.class)
public class MProfileTest {

   @Mock
   Context mockContext;
   @Mock
   SharedPreferences mockPrefs;
   @Mock
   SharedPreferences.Editor mockEditor;

   @Before
   public void before() throws Exception {

      Mockito.when(mockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mockPrefs);
      Mockito.when(mockContext.getSharedPreferences(anyString(), anyInt()).edit()).thenReturn(mockEditor);

      Mockito.when(mockPrefs.getString("YOUR_KEY", null)).thenReturn("YOUR_VALUE");
   }

   @Test
   public void anyTest() {
      // Any shared preference you can call
      // Assert.assertTrue();
      String val = _mockPrefs.getString("YOUR_KEY", null); // It returns YOUR_VALUE
   }
}

If you face any issues on importing mock framework just make sure you have added the depencencies in your app/build.gradle file.

https://developer.android.com/training/testing/unit-testing/local-unit-tests#setup


If you want to use real shared prefrence as your device by storing all the data in memory, follow the below code.

Get the MockSharedPreference.java file from this Gist https://gist.github.com/aslamanver/f74a2b3d450fda251d47a0d38b44edb7

@Mock
Context mockContext;

MockSharedPreference mockPrefs;
MockSharedPreference.Editor mockPrefsEditor;

@Before
public void before() {

    mockPrefs = new MockSharedPreference();
    mockPrefsEditor = mockPrefs.edit();

    Mockito.when(mockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mockPrefs);
}
Googlian
  • 6,077
  • 3
  • 38
  • 44
  • Note that static functions like `SharedPrefs prefs = PreferenceManager.getDefaultSharedPreferences(context);` cannot be mocked by Mockito. So you may have to convert it in your app to use a non-static method, like this: `SharedPreferences prefs = context.getSharedPreferences(context.getPackageName() + "_preferences", Context.MODE_PRIVATE);` – Mr-IDE Dec 08 '19 at 17:27
  • 1
    This method won't affect the application since it's running in testing environment with `MockitoJUnitRunner` – Googlian Dec 09 '19 at 06:43
3

There is a better way to mock SharedPreferences, IMHO. I like Mockito, but it is unproductive to mock SharedPreferences in every test.

Luckily, we can use the shared-preferences-mock library. This library implements SharedPrefences on JVM, so it behaves like a real class. Moreover, it is possible to write local unit tests.

For your case:

import com.github.ivanshafran.sharedpreferencesmock.SPMockBuilder;

class Test {
    private Context context;
    private SharedPreferences sharedPreferences;

    @Before
    public void setUp() {
        this.sharedPreferences = new SPMockBuilder().createSharedPreferences();
        this.context = Mockito.mock(Context.class);         
        Mockito.when(context.getSharedPreferences(Constants.LOGGED_USER_PREFERENCES,0))
            .thenReturn(sharedPreferences);
    }

    @Test
    public void test() {
        sharedPreferences.edit().putString(Constants.LOGGED_USERNAME, "admin").commit();
        String value = Auth.getLoggedUser(context);
        asssertEquals("admin", value);
    }

}

Add it to your root build.gradle at the end of repositories:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

Add the dependency:

dependencies {
    testImplementation 'com.github.IvanShafran:shared-preferences-mock:1.0'
}
Ivan Shafran
  • 591
  • 2
  • 17