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?
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
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));