2

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.
Community
  • 1
  • 1
JJD
  • 50,076
  • 60
  • 203
  • 339

2 Answers2

1

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));
alayor
  • 4,537
  • 6
  • 27
  • 47
  • I tried that but `BooleanPrefField` is `final`. Error: `Cannot mock/spy class org.androidannotations.api.sharedpreferences.BooleanPrefField Mockito cannot mock/spy because: final class` – JJD May 18 '17 at 14:39
  • And `BooleanPrefField` is package private which prevents me from instantiating a new object such as `new BooleanPrefField(true)`. – JJD May 18 '17 at 14:43
  • You can try to use `SharedPreferencesHelper.booleanField("", false)` – alayor May 18 '17 at 14:52
  • Please explain "... simply return an object of that class in your mocking ...". I could not see `SharedPreferencesHelper.booleanField` exists as a static method. – JJD May 18 '17 at 14:52
  • 1
    I see this class exists in `org.androidannotations.annotations.sharedpreferences` package. There must be a way to create a `BooleanPrefField` object. http://javadox.com/org.androidannotations/androidannotations-api/3.2/org/androidannotations/api/sharedpreferences/SharedPreferencesHelper.html – alayor May 18 '17 at 15:05
  • Yes. I can extend from `SharedPreferencesHelper` and implement a method such as `public BooleanPrefField enabledPrefField() { return super.booleanField("enabled", false); }` but which `SharedPreferences` object do I pass to the constructor? It is invoked inside `BooleanPrefField.getOr()` as in `sharedPreferences.getBoolean(key, defaultValue);`. – JJD May 18 '17 at 15:35
  • 1
    You could pass a mocked `SharedPreferences` object. – alayor May 18 '17 at 15:47
  • I updated my question with the current implementation. However, when persisting the value via `put` mocking fails again due to the `final` restriction of `BooleanPrefField`. – JJD May 22 '17 at 10:29
  • I created a simplified sample project [here](https://github.com/johnjohndoe/EnabledTest). – JJD May 22 '17 at 13:50
  • Thanks for you input - I finally were able to mock `final` classes by using `mockito-inline`. – JJD May 22 '17 at 15:32
0

Have you tried running your tests with @RunWith(MockitoJUnitRunner.class) instead of MockitoAnnotations? The runner should initialize all @Mock annotated fields automatically and get rid of that NPE.

Also maybe following answer could help you further.

Community
  • 1
  • 1
QBrute
  • 4,405
  • 6
  • 34
  • 40
  • The `prefs` object is initialized. That is not the problem. The NPE occurs when I invoke `.get` on `prefs.enabled()` which returns a `BooleanPrefField` in the implementation case. – JJD May 18 '17 at 14:34