Hot questions for Using Mockito in sharedpreferences

Top 10 Java Open Source / Mockito / sharedpreferences

Question:

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?


Answer:

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();
}

Question:

I have a helper class to save user object to shared preferences. I have used a serialize(): String function and a create(serializedString: String) function in my User data model. They use GSon serializer and are working good as suggested by the unit tests on them.

Now my helper class is called SharedPreferenceUserStore.kt which takes a Context object. The code is:

class SharedPreferenceUserStore(context: Context) {
    companion object {
        val TAG = SharedPreferenceUserStore::class.java.simpleName
    }

    var userLocalSharedPref: SharedPreferences =
        context.getSharedPreferences(USER_LOCAL_STORE_SHARED_PREF_NAME, Context.MODE_PRIVATE)

    /*
    Store the required data to shared preference
     */
    @SuppressLint("ApplySharedPref")
    fun storeUserData(user: User) {
        val userLocalDatabaseEditor = userLocalSharedPref.edit()
        val serializedData = user.serialize()

        userLocalDatabaseEditor.putString(
            USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY,
            serializedData
        )
        if (userLocalDatabaseEditor.commit()) {
            Log.d(TAG, " Store Commit return true")
        }
    }


    /*
    Clear all the locally stored data from the shared pref
     */
    @SuppressLint("ApplySharedPref")
    fun clearUserData() {
        val userLocalDatabaseEditor = userLocalSharedPref.edit()
        userLocalDatabaseEditor.clear()
        userLocalDatabaseEditor.commit()
    }

    fun getLoggedInUser(): User? {
        val stringUser = userLocalSharedPref.getString(
            USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")

        return if (stringUser==null || stringUser == ""){
            null
        } else{
            User.create(stringUser)
        }
    }

And I have written some unit tests for this helper class as follows:

@RunWith(JUnit4::class)
class SharedPreferenceUserStoreTest {

    lateinit var sharedPreferenceUserStore: SharedPreferenceUserStore
    lateinit var user: User

    //to be mocked
    lateinit var sharedPreferences: SharedPreferences
    lateinit var sharedPreferencesEditor: SharedPreferences.Editor
    lateinit var context: Context

    @Before
    fun setUp() {
        //mocking Context and SharedPreferences class
        context = mock(Context::class.java)
        sharedPreferences = mock(SharedPreferences::class.java)
        sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)

        //specifying that the context.getSharedPreferences() method call should return the mocked sharedpref
        `when`<SharedPreferences>(context.getSharedPreferences(anyString(), anyInt()))
            .thenReturn(sharedPreferences)
        //specifying that the sharedPreferences.edit() method call should return the mocked sharedpref editor
        `when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
        //specifying that the sharedPreferencesEditor.putString() method call should return the mocked sharedpref Editor
        `when`(sharedPreferencesEditor.putString(anyString(), anyString())).thenReturn(
            sharedPreferencesEditor
        )
        `when`(sharedPreferences.getString(anyString(), anyString())).thenReturn("")

        //instantiating  SharedPreferenceUserStore from the mocked context
        sharedPreferenceUserStore = SharedPreferenceUserStore(context)


        user = User(
            35,
            "Prashanna Bhandary",
            "prashanna.bhandary@gmail.com",
            "dd58a617ea618010c2052cb54079ad67.jpeg",
            "98********",
            "test address 01",
            1,
            "yes",
            "2019-08-30 04:56:43",
            "2019-08-30 05:14:47",
            0
        )
    }

    @After
    fun tearDown() {
    }

    @Test
    fun passUser_storeUserData() {
        sharedPreferenceUserStore.storeUserData(user)

        verify(sharedPreferencesEditor).putString(
            Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY,
            user.serialize()
        )
        verify(sharedPreferencesEditor).commit()
    }

    @Test
    fun testClearUserData() {
        sharedPreferenceUserStore.clearUserData()

        verify(sharedPreferencesEditor).clear()
    }


    @Test
    fun testGetLoggedInUser_storeNotCalled() {
        //calling getLoggedInUser() without calling storeUserData() should give null
        assertEquals(null, sharedPreferenceUserStore.getLoggedInUser())
        //verify that getString() was called on the shared preferences
        verify(sharedPreferences).getString(Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")
    }

    @Test
    fun testGetLoggedInUser_storeCalled(){

        //call getLoggedInUser(), we are expecting null
        assertNull(sharedPreferenceUserStore.getLoggedInUser())

        //verify that getString() was called on the shared preferences
        verify(sharedPreferences).getString(Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")
    }
}

As I am really new to Unit Testing and Mocking libraries like Mockito. Now my question is are my tests any good? and I wanted to test if the getLoggedInUser() funciton of my helper class is doing what it is supposed to do (ie. get logged in user if shared pref has it), how do I do that?

In addition do suggest me any improvements I can make to my test or the helper class itself. Thank you.


Answer:

Judging your test for what it is - A unit test running on a host machine with Android dependencies mocked with Mockito - it looks fine and like what you would expect.

The benefit-to-effort ratio of such tests are debatable, though. Personally I think it would be more valuable to run such a test against the real SharedPreferences implementation on a device, and assert on actual side effects instead of verifying on mocks. This has a couple of benefits over mocked tests:

  • You don't have to re-implement SharedPreferences with mocking
  • You know that SharedPreferenceUserStore will work with the real SharedPreferences implementation

But, such tests also have a debatable benefit-to-effort ratio. For a solo developer project, think about what kind of testing that is most important. Your time is limited so you will only have time to spend on writing the most important kind of tests.

The most important kinds of tests are the ones that test your app in the same way your users will use it. In other words, write high-level UI Automator tests. You can write how many mocked or on-device unit tests as you want. If you don't test that your entire app as a whole works, you will not know that it works. And if you don't know that your app as a whole works, you can't ship it. So in some way you have to test your app in its entirety. Doing it manually quickly becomes very labour intensive as you add more and more functionality. The only way to continually test your app is to automate the high-level UI testing of your app. That way you will also get code coverage that matters.

One big benefit of high-level UI testing that is worth pointing out is that you don't have to change them whenever you change some implementation detail in your app. If you have lots of mocked unit tests, you will have to spend a lot of time to refactor your unit tests as you refactor the real app code, which can be very time consuming, and thus a bad idea if you are a solo developer. Your UI Automator tests do not depend on low-level implementation details and will thus remain the same even if you change implementation details.

For example, maybe in the future you want to use Room from Android Jetpack to store your user data instead of SharedPreference. You will be able to do that without changing your high level UI tests at all. And they will be a great way to regression test such a change. If all you have are mocked unit tests, it will be a lot of work to rewrite all relevant unit tests to work with Room instead.

Question:

I am trying to write unit tests by mocking Android Shared preference using Mockito Library, however it doesn't work for me even after multiple tries. Please check my sample code below and let me know what went wrong with it

I am currently using "JCAndKSolutions/android-unit-test" android junit plug-in.

My code is as below:

PreferenceHelper.java

 public class PreferencesHelper
    {
         private SharedPreferences mSharedPreferences;

         static String PREFERENCE_USER;

         public PreferencesHelper(SharedPreferences mSharedPreferences)
        {
            this.mSharedPreferences = mSharedPreferences;
        }

        public void setUserName(String userName)
        {
            mSharedPreferences.edit().putString(PREFERENCE_USER,   "Vkc").apply();
        }

        public String getUserName()
        {
            return mSharedPreferences.getString(PREFERENCE_USER, null);

        }

    }


PreferenceHelperTest.java

public class PreferencesHelperTest extends TestCase
{
    @Mock
    SharedPreferences mSharedPreference;

    @Mock
    SharedPreferences.Editor mEditor;

    @Mock
    Context context;

    @Mock
    PreferenceManager mPreferenceManager;

    PreferencesHelper mPrefHelper;

    public void setUp() throws Exception
    {
        initMocks(this);
    }

    public void testSetUserName(){

        final InOrder inOrder = inOrder(mEditor);
        when(mSharedPreference.edit()).thenReturn(mEditor);
        mPrefHelper = new PreferencesHelper(mSharedPreference);

        mPrefHelper.setUserName("Grapes");
        inOrder.verify(mEditor).putString(PreferencesHelper.PREFERENCE_USER, "Grapes");
        inOrder.verify(mEditor).apply();
    }
}

If I run this junit test I get following error

java.lang.NullPointerException
    at     com.example.aag.testhelloworld.PreferencesHelper.setUserName(PreferencesHelper.java:21)
    at     com.example.aag.testhelloworld.PreferencesHelperTest.testSetUserName(PreferencesHelperTest.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at     sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at     org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
at     com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

While debugging I found that when putString() method is invoked nullPointer exception is thrown.

Any help or clues where it went wrong??


Answer:

I believe the issue is that because you are mocking the Editor, when you call putString() it will return a different object. Try mocking the putString() method as well, like so: when(mEditor.putString(anyString(), anyString())).thenReturn(mEditor);

Question:

I am new to unit testing in Android and my attempt is to assertTrue that the data is successfully passed to a method and saved in SharedPreferences. This is my test so far:

public class AuthTest {

    Authorization authorization = new Authorization();

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test_IfAuthIsSaved() {
        //test if the auth object is saved in the auth object is saved as a.. 
        //..json string in Shared preferences
        Auth auth = mock(Auth.class);
        authorization.saveAuth(auth);

        //test if the auth is received and saved to sharedpreferences
    }

}

saveAuth method:

public void saveAuth(Auth auth) {
    editor.putString("USER_AUTH", new Gson().toJson(auth));
    editor.commit();
}

What would the assertion look like for this?


Answer:

You are mocking Auth which does not interact with anything in your code so you can't do any assertions on it.

You need to change your approach of testing:

1st Approach
  • Mock SharedPreferences.Editor and inject it inside Authorization.
  • Instantiate a new Auth object and invoke authorization.saveAuth(auth).
  • Assert that editorMock.putString() is invoked with the expected json.
  • Assert that editorMock.commit() is invoked.

This approach has some drawbacks:

  • your test is coupled with the implementation.
  • if you decide to store the Auth data in some other kind of form you would need to change the test
  • you are not really testing behavior (which you actually want to do)
2nd Approach
  • Create a fake implementation of SharedPreferences.Editor and inject it inside Authorization.
  • Create a new Auth object and invoke authorization.saveAuth(auth).
  • Retrieve auth after saving it by invoking authorization.getAuth() and assert that it is the same Auth that you saved.

Drawbacks: * you need to create a fake implementation of ``SharedPrefereces.Editor``` for test purposes that simulates the same behavior

Advantages: * your test is not coupled with the implementation * you are free to change the implementation without changing the test * you are testing behavior not methods

Some references to backup the second approach:

Now, from a technical point of view, retrieval of a stored object is really a subset of creation, since ...

Domain Driven Design by Eric Evans

Question:

I'm starting to use Kotlin on a little demo android app. I've created a sharedpreferences helper class which i'm trying to test with Junit and Mockito. Below is my sharedprefshelper:

public class SharedPrefsHelperImp( cont : Context) : SharedPrefsHelper {

val prefsname: String = "prefs"
var prefs: SharedPreferences? = null
var edit: SharedPreferences.Editor? = null


init {
    prefs = cont.getSharedPreferences(prefsname, Context.MODE_PRIVATE)
    edit = prefs!!.edit()
}



override fun getPrefsStringValue(key: String) : String {
    return prefs!!.getString(key, "")
}

 override fun addPrefsStringVal( key : String,  value: String)  {
        edit!!.putString(key, value).commit()
}

override fun getSharedPrefsBool(key : String): Boolean {
    return prefs!!.getBoolean(key, false)
}

override fun addSharedPrefsBool(key : String, value : Boolean) {
        edit!!.putBoolean(key, value).commit()
}
}

here is my test class:

class SharedPrefsHelperImpTest {

@Mock var  cont : Context? = null
@Mock var mockprefs : SharedPreferences? = null
@Mock var mockprefsedit : SharedPreferences.Editor? = null
var prefshelper : SharedPrefsHelper? = null

@Before
fun setUp() {

    //MockitoAnnotations.initMocks(this)
    cont = Mockito.mock(Context::class.java)
    mockprefs = Mockito.mock(SharedPreferences::class.java)
    mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)

    `when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
    `when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)

    prefshelper = SharedPrefsHelperImp(cont!!)
}

@Test
fun testNotNull(){
    Assert.assertNotNull(cont)
    Assert.assertNotNull(mockprefs)
    Assert.assertNotNull(mockprefsedit)
}

@Test
fun testItemAdded()
{
    prefshelper!!.addPrefsStringVal("thing", "thing")
    verify(mockprefsedit)!!.putString(anyString(), anyString())
}

@Test
fun testGetString()
{
    prefshelper!!.getPrefsStringValue("key")
    verify(mockprefs)!!.getString("key", "")
}

}

Issue is when I call addPrefsValueString() in the helper. the line

edit!!.putString(key, value).commit()

throws a null pointer exception? not sure why? I've setup the mock sharedprefs and sharedpreferences.Edit in the test class method annotated with @Before (shown below)

@Before
fun setUp() {

    //MockitoAnnotations.initMocks(this)
    cont = Mockito.mock(Context::class.java)
    mockprefs = Mockito.mock(SharedPreferences::class.java)
    mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)

    `when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
    `when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)

    prefshelper = SharedPrefsHelperImp(cont!!)
   }

i'm sure my code is less than optimal.

EDIT:

Here's my fix for the testItemAdded() method. need to return the mock preferences editor on the first call.

@Test
fun testItemAdded()
{
    `when`(mockprefsedit?.putString(anyString(), anyString())).thenReturn(mockprefsedit)
    `when`(mockprefsedit?.commit()).thenReturn(true)
    prefshelper!!.addPrefsStringVal("thing", "thing")

    verify(mockprefsedit)!!.putString(anyString(), anyString())
    verify(mockprefsedit)!!.commit()
}

Answer:

You should set expectations for the call below, on your mock object (mockprefsedit). As well for the object returned, on which commit is invoked.

edit!!.putString(key, value)

thanks Sriram

Question:

In a project I am using AndroidAnnotations to generate SharedPreferences:

import org.androidannotations.annotations.sharedpreferences.DefaultBoolean;
import org.androidannotations.annotations.sharedpreferences.SharedPref;

@SharedPref(value = SharedPref.Scope.UNIQUE)
public interface MySharedPreferences {

    @DefaultBoolean(false)
    boolean enabled();
}

The generated class can be used as follows:

preferences.enabled().get();
preferences.enabled().put(true);

I am trying to write a unit test which checks some logic. There I want to mock the preferences:

@Mock MyPreferences_ prefs;
MyLogic logic;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    logic = new Logic();
}

@Test
public void testEnabled() throws Exception {
    when(prefs.enabled().get()).thenReturn(false);
    assertThat(logic.isEnabled()).isEqualTo(false);
}

However, accessing prefs.enabled() throws a NullPointerException:

java.lang.NullPointerException at com.example.MyLogicTest.isValuesStoredProperly(MyLogicTest.java) ...

Is it possible to mock a chained method call (including null objects) with Mockito?

Update

As an update based on the helpful suggestions by alayor I changed my implementation as follows:

public class MyLogicTest {

    @Mock SharedPreferences        prefs;
    @Mock CustomSharedPreferences_ prefs_;

    MyLogic logic;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        logic = new MyLogic();
    }

    @Test
    public void testEnabled() throws Exception {
        MockPrefs mockPrefs = new MockPrefs(prefs);
        when(prefs_.enabled()).thenReturn(mockPrefs.getEnabledPrefField());
        doNothing().when(prefs_.enabled()).put(anyBoolean()); // <-- fails
        when(prefs_.enabled().get()).thenReturn(false);
        assertThat(logic.isEnabled()).isEqualTo(false);
    }

    private class MockPrefs extends SharedPreferencesHelper {

        public MockPrefs(SharedPreferences sharedPreferences) {
            super(sharedPreferences);
        }

        public BooleanPrefField getEnabledPrefField() {
            return super.booleanField("enabled", enabledOld);
        }

    }
}

This still fails here:

doNothing().when(prefs_.enabled()).put(anyBoolean());

The BooleanPrefField object from prefs_.enabled() is final and cannot be mocked.

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
-> at MyLogicTest.testEnabled

E.g. thenReturn() may be missing.
Examples of correct stubbing:
    when(mock.isOk()).thenReturn(true);
    when(mock.isOk()).thenThrow(exception);
    doThrow(exception).when(mock).someVoidMethod();
Hints:
 1. missing thenReturn()
 2. you are trying to stub a final method, which is not supported
 3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
Sample project
Solution
  • Please find the working in above sample project.

Answer:

In order to mock the chained method call you could use this annotation in MyPreferences_

@Mock(answer = Answers.RETURNS_DEEP_STUBS).

But I recommend that you mock the result of calling prefs.enabled() instead.

@Mock 
BooleanPrefField booleanPrefField;

@Test
public void testEnabled() throws Exception {
    when(prefs.enabled()).thenReturn(booleanPrefField);
    when(booleanPrefField.get()).thenReturn(false);
    assertThat(logic.isEnabled()).isEqualTo(false);
}

Note: You should substitute MyObject with the type of object that prefs.enabled() returns. Using this way you have more control over the behavior of your method calls.

UPDATE: In case BooleanPrefField is final, you could simply return an object of that class in your mocking.

when(prefs.enabled()).thenReturn(SharedPreferencesHelper.booleanField("", false));

Question:

I'm trying to use Mockito to test a settings manager which saves data through SharedPreferences.

Since SharedPreferences makes use of Context, I need to use mock classes.

This is my settings manager class:

public class SettingsManager implements ISettingsManager {

    protected SharedPreferences prefs;

    public SettingsManager(SharedPreferences prefs) {
        this.prefs = prefs;
    }

    private boolean getBooleanPreference(String key) {
        return prefs.getBoolean(key, true);
    }

    private void setBooleanPreference(boolean enabled, String key) {
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean(key, enabled);
        editor.commit();
    }
}

This is the test case I wrote:

Context mContext = Mockito.mock(Context.class);
SharedPreferences mSharedPreference = Mockito.mock(SharedPreferences.class);
SharedPreferences.Editor mEditor = Mockito.mock(SharedPreferences.Editor.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(mSharedPreference.edit()).thenReturn(mEditor);
Mockito.when(mEditor.commit()).thenReturn(true);
Mockito.when(mEditor.putBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(mEditor);

SettingsManager manager = new SettingsManager(mSharedPreference);
boolean current = manager.areNotificationsEnabled();
manager.setNotificationsEnabled(!current);
boolean newValue = manager.areNotificationsEnabled();
Assert.assertTrue(newValue != current);

The problem is when I set the setNotificationsEnabled flag, the newValue remains the same of current: SharedPreferences does not persist data. How can I save data to SharedPreferences while testing?


Answer:

Robolectric is an option for this kind of integration test.

Robolectric provides test doubles called "shadows" of common Android classes like Context, SQLiteDatabase and SharedPreferences. The tests you write run in test in your IDE (not androidTest on an emulator or test device) so it is easier to configure tools for test coverage.

The shadow SharedPreference is sandboxed as well so it won't interfere with the actual SharedPreferences on a device.

Question:

Consider below testing class:

@RunWith(MockitoJUnitRunner::class)
class DatabaseManagerTest {

    private var someStringValue = "test"
    private var mockDatabaseManager: DatabaseManager? = null
    @Mock
    internal var mockSharedPreferences: SharedPreferences? = null
    @Mock
    internal var mockEditor: SharedPreferences.Editor? = null

    @Before
    fun initMocks() {
         mockDatabaseManager = createMockSharedPreference()        
    }

    private fun createMockSharedPreference(): DatabaseManager {
        `when`<String>(
            mockSharedPreferences?.getString(
               Matchers.eq(DatabaseManager.SOME_UNIQUE_KEY),
               Matchers.anyString()
            )
        ).thenReturn(someStringValue)

        `when`(mockEditor?.commit()).thenReturn(true)

        `when`<SharedPreferences.Editor>(mockSharedPreferences?.edit()).thenReturn(mockEditor)

        return DatabaseManager(mockSharedPreferences!!)
    }

    @Test
    fun sharedPreferencesHelperUpdateAndReadStoredInformation() {
        val success = mockDatabaseManager?.insert(someStringValue)

        assertThat(
            "Checking that update method returns true",
            success, `is`(true)
        )

        val savedSharedPreferenceEntry = mockDatabaseManager?.readSomeValue()

        assertThat(
           "Checking that value is stored correctly",
           someStringValue,
           `is`(equalTo(savedSharedPreferenceEntry))
        )
    }
}

Here is the Database class:

class DatabaseManager(private val preferences: SharedPreferences) {

    companion object {
        const val SOME_UNIQUE_KEY = "some_unique_key"
    }

    fun insert(response: String): Boolean {
        try {
            val editor = preferences.edit()
            editor.putString(SOME_UNIQUE_KEY, response)
            editor.apply()

        } catch (e: Exception) {
            e.printStackTrace()
        }
        return true
    }

    fun readSomeValue(): String? {
        return try {
            val someValue = preferences.getString(SOME_UNIQUE_KEY, null)
            someValue

        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }
}

When the old Mockito library is being used (1.10.19), the test passes.

When switching mockito-core to 3.0.0, I get 2 deprecated warnings for: @RunWith(MockitoJUnitRunner::class) and Matchers. After solving these 2 deprecated warnings, I am expecting readSomeValue() to return "test" but readSomeValue() returns null.

//    testImplementation 'org.mockito:mockito-core:3.0.0'
    testImplementation 'org.mockito:mockito-core:1.10.19'

Answer:

First of all you should change the first bit of your test as shown below:

@Mock
lateinit var mockSharedPreferences: SharedPreferences
@Mock
lateinit var mockEditor: SharedPreferences.Editor

@Before
fun initMocks() {
    MockitoAnnotations.initMocks(this)
    mockDatabaseManager = createMockSharedPreference()
}

You are not initialising your mock objects which hence the member variables are not assigned values.

Second of all, SharedPreferences is an android component and cannot be unit tested the way you are trying to. In your case you are trying to insert something, test the response of your insert function which is always true anyway. Then, you are trying to fetch that value. You shouldn't test the framework but your code.

If you want to do structure your tests like that, I will recommend Robolectric.

I will suggest structuring your tests differently though, like verifying that the right thing is called when you are expecting to. For example,

@Test
fun `shared preferences insert`() {
    mockDatabaseManager?.insert(someStringValue)

    Mockito.verify(mockSharedPreferences).edit()
    Mockito.verify(mockEditor).putString(
        Mockito.eq(DatabaseManager.SOME_UNIQUE_KEY),
        Mockito.eq(someStringValue)
    )
    Mockito.verify(mockEditor).apply()
}


@Test
fun `shared preferences readSomeValue`() {
    mockDatabaseManager?.readSomeValue()

    Mockito.verify(mockSharedPreferences).getString(
        Mockito.eq(DatabaseManager.SOME_UNIQUE_KEY),
        Mockito.isNull()
    )
}

Question:

I have made a simple instrumented test to verify that if the data read from the SharedPreferences is displayed properly on the UI.Both data-retrieving and displaying actions are performed in Activity's onResume()method. But the problem is,even if I've mocked the preference object and defined the fake return value,the activity still read data from the real preference,ignoring when(...).thenReturn(...)statement.Does anyone have any idea?

@RunWith(AndroidJUnit4.class)
public class EditProfileActivityTest {

    @Mock
    private UserPreference userPreference;
    private String FAKE_NAME = "Test";

    @Rule
    public ActivityTestRule<EditProfileActivity> activityTestRule = new ActivityTestRule(EditProfileActivity.class,true,false);

    @Before
    public void setUp(){

        //Set fake SharedPreferences
        MockitoAnnotations.initMocks(this);
        when(userPreference.getName()).thenReturn(FAKE_NAME);

        //Start Activity
        Intent intent = new Intent();
        activityTestRule.launchActivity(intent);
    }

    @Test
    public void showUserData() throws Exception{
        onView(withId(R.id.name_tv)).check(matches(withText(FAKE_NAME)));
    }
}    

where UserPreference is a custom class which simply wraps SharedPreference class and contains lots of getters and setters.This is its constructor

public UserPreference(Context context) {
    this.context = context;
    sharedPreferences = this.context.getSharedPreferences("Pref", Context.MODE_PRIVATE);
    prefEditor = sharedPreferences.edit();
}  

and one of its getter and setter:

public String getName() {
    return sharedPreferences.getString(context.getString(R.string.pref_name), "Guest");
}    
public void saveName(String name){
    prefEditor.putString(context.getString(R.string.pref_name), name);
    prefEditor.apply();
}

[EDIT] Simplified version of my original Activity:

public class EditProfileActivity extends AppCompatActivity{
    //...
    private UserPreference userPreference;
    //...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userPreference = new UserPreference(this);
        setContentView(R.layout.activity_edit_profile);
        //...
    }
    @Override
    protected void onResume() {
        super.onResume();
        //...
        String name = userPreference.getName();
        nameEdt.setText(name);  //Display the name on an EditText
        //...
    }
}

Answer:

The UserPreference mock is created, but the activity still uses the one created in its onCreate method. You need to set that activity's userPreference field to your mock.

There are a few ways to do that:

  • Add a setter method for the userPreference field and call it in your @Before method:

    @Before
    public void setUp(){
        ...
        EditProfileActivity activity = activityTestRule.launchActivity(intent);
        activity.setUserPreference(mockedUserPreference);
    }
    

    This is simple but ugly: you alter the activity solely to accomodate the test.

  • Set the userPreference field via reflection:

    @Before
    public void setUp(){
        ...
        EditProfileActivity activity = activityTestRule.launchActivity(intent);
        Field userPreferenceField = activity.getClass().getDeclaredField("userPreference");
        field.setAccessible(true);
        userPreferenceField.set(activity, mockedUserPreference);
    }
    

    This is a brittle test: changing the field name breaks it without compile error. The activity doesnt have to be altered, though, so it is useful when you cant change it.

  • Don't instantiate the UserPreference in the onCreate method. In plain Java i'd add it as a constructor argument, but i don't know if that works as easily with Android. Maybe use a dependency injection framework, they're perfect to use with mocking: Android and Dependency Injection

Question:

I am trying to mock a simple shared preferences using Mockito . Since, the examples on google are too complicated to make anything out of it, I decided to go ahead on my own. The shared preferences are setup using dagger. It crashes with NPE in the saveString method in the SharedPreferenceManager class on the putString line.

@Module
public class StudentModule {
    @Provides
    @Singleton
    static Context getContext(Application application) {
        return application.getApplicationContext();
    }

    @Provides
    @Singleton
    static SharedPreferences getSharedPreferences(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context);
    }
}

There is a manager class:

public class SharedPreferenceManager {
    private SharedPreferences sharedPreferences;
    private Context context;
    @Inject public SharedPreferenceManager(SharedPreferences sharedPreferences, Context context){
        this.sharedPreferences=sharedPreferences;
        this.context=context;
    }
    public String doSomething(){

        return sharedPreferences.getString("s","");
    }
    public void saveString(String s){
        System.out.println(sharedPreferences.getClass().getSimpleName());
        SharedPreferences.Editor editor=sharedPreferences.edit();
        editor.putString("s","bis").apply();

    }
}

Here is the test:

@RunWith(MockitoJUnitRunner.class)

public class MockTest {
    @InjectMocks
    SharedPreferenceManager sharedPreferenceManager;
    @Mock SharedPreferences sharedPreferences;


    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);



    }
    @Test
    public void isSharedPefWorking(){
        sharedPreferenceManager.saveString("bis");
        assertEquals("bis",sharedPreferenceManager.doSomething());

    }

}

Answer:

SharedPreferences uses a SharedPreferences.Editor which you're not currently mocking.

You would need to do something like the following to mock and verify the behaviour of your SharedPreferenceManager.

@RunWith(MockitoJUnitRunner.class)
public class MockTest {
    @InjectMocks
    SharedPreferenceManager sharedPreferenceManager;
    @Mock
    SharedPreferences sharedPreferences;
    @Mock
    SharedPreferences.Editor sharedPreferencesEditor;

    final String savedString = "savedString";

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        when(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor);
        when(sharedPreferencesEditor.putString(anyString(), anyString())).thenReturn(sharedPreferencesEditor);
    }

    @Test
    public void saveString() {
        sharedPreferenceManager.saveString(savedString);
        verify(sharedPreferencesEditor).putString("s", savedString);
    }

    @Test
    public void getString() {
        when(sharedPreferences.getString("s","")).thenReturn(savedString);

        String preferenceString = sharedPreferenceManager.doSomething();

        assertEquals(preferenceString, savedString);
    }

}

Question:

I have a problem with Mockito. I have written tests for my presentation layer. I used mockito to mock some dependencies. Everything was working fin for last 2 months and suddenly I started receiving an exception:

java.lang.NoClassDefFoundError: Landroid/content/SharedPreferences;

Previously there were no problem with it at all. I have not changed the version of Mockito and JUnit in my gradle and it looks like:

testCompile "org.mockito:mockito-core:2.+"
testCompile 'junit:junit:4.12'

And my test class looks like:

@RunWith(MockitoJUnitRunner.class) 
public class PostDetailsPresenterTest {
    @Mock
     SharedPreferences preferences;

     @Before
     public void setUp() {
         SharedPrefsUtils utils = new SharedPrefsUtils(preferences);
     } 
}

But after starting tests I keep receiving an exception. Does someone had similar problem and know how to deal with it?


Answer:

Try to clean and rebuild. Then ./gradlew clean test. If that doesn't help, remove the .gradle folder from your project and rebuild.