1

I have a problem with instrumental test that is checking activity that in one of methods saves its state to shared preferences. Tested code looks like that:

initialPresenter.getLocalData().edit()
                    .putString("SessionDetails", new Gson().toJson(sessionData))
                    .putBoolean("relog", false)
                    .apply();

LocalData is injected into presenter by dagger2. I've created mocks for it and I'm repleacing them so everything works fine there; eg.

when(localData.getBoolean("signed_using_email", false)).thenReturn(true);

Problem occurs when I'm trying to somehow disable or ommit editing data. I've created another mock; this time of editor so when SharedPref calls edit it gets explicit mock;

@Mock SharedPreferences.Editor mEditor;
.
.
.
when(localData.edit()).thenReturn(mEditor);

but then I get error:

Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'android.content.SharedPreferences$Editor android.content.SharedPreferences$Editor.putBoolean(java.lang.String, boolean)' on a null object reference

Which btw. is freaking weird, why on putBoolean no putString? It seems like first mock works just fine, but then it gets nested (?) and throws error.

Also tried another approach, instead of stubbing/replacing answer I've used doNothing;

doNothing().when(localData).edit();

But it also caused similar problem throwing error:

org.mockito.exceptions.base.MockitoException:
Only void methods can doNothing()!
Example of correct use of doNothing():
doNothing().
doThrow(new RuntimeException())
.when(mock).someVoidMethod();
Above means:
someVoidMethod() does nothing the 1st time but throws an exception the 2nd time is called

Any ideas how to fix it? I don't need to save any state, I can mock it later, which is fine because I'll get documentation then by writing these tests. Earlier I was using PowerMockito to suppress whole method that uses sharedPreferences but this solution doesn't seem to be to good.

Kamajabu
  • 596
  • 1
  • 5
  • 19
  • 1
    Your problem is essentially how to mock the 'builder' syntax of `SharedPreferences.Editor`. Check out [Jeff Bowman's answer](https://stackoverflow.com/questions/8501920/how-to-mock-a-builder-with-mockito) on how to do this with Mockito. In other words, try `@Mock(answer = RETURNS_SELF) SharedPreferences.Editor mEditor;` – David Rawson Dec 07 '16 at 09:10
  • @DavidRawson thanks! I've changed creating mock to mEditor = mock(SharedPreferences.Editor.class, RETURNS_DEEP_STUBS); and it seems to work. Could you post your comment as answer so I can accept it? :) – Kamajabu Dec 07 '16 at 09:57

1 Answers1

3

The problem here is that SharedPreferences.Editor has a 'builder' syntax where each call putString(), putBoolean() etc. return the Editor.

When you mock this object, you want to mimic this behaviour by having the mock return itself each time one of those methods is invoked.

As per Jeff Bowman's answer on mocking builder syntax with Mockito you can do this with the following change in your code:

@Mock(answer = RETURNS_SELF) SharedPreferences.Editor mEditor;

Alternatively, you may just want to use RETURNS_DEEP_STUBS:

mEditor = mock(SharedPreferences.Editor.class, RETURNS_DEEP_STUBS);
David Rawson
  • 20,912
  • 7
  • 88
  • 124