Имитация вызовов связанных методов в AndroidAnnotations SharedPreferences

В проекте я использую AndroidAnnotations для создания 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();
}

Сгенерированный класс можно использовать следующим образом:

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

Я пытаюсь написать модульный тест, который проверяет некоторую логику. Там я хочу издеваться над предпочтениями:

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

Однако при доступе к prefs.enabled() выдается NullPointerException:

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

Можно ли издеваться над цепным вызовом метода (включая нулевые объекты) с помощью Mockito?

Обновлять

В качестве обновления, основанного на полезных предложениях alayor, я изменил свою реализацию следующим образом:

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

    }
}

Это по-прежнему сбой здесь:

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

Объект BooleanPrefField из prefs_.enabled() является final и не может быть имитирован.

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

Пример проекта

Решение

  • Пожалуйста, найдите работу в приведенном выше примере проекта.

person JJD    schedule 18.05.2017    source источник
comment
Что возвращает prefs.enabled()?   -  person alayor    schedule 18.05.2017


Ответы (2)


Чтобы смоделировать вызов связанного метода, вы можете использовать эту аннотацию в MyPreferences_

@Mock(answer = Answers.RETURNS_DEEP_STUBS).

Но я рекомендую вместо этого издеваться над результатом вызова prefs.enabled().

@Mock 
BooleanPrefField booleanPrefField;

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

Примечание. Вы должны заменить MyObject типом объекта, который возвращает prefs.enabled(). Используя этот способ, вы лучше контролируете поведение вызовов методов.

ОБНОВЛЕНИЕ: в случае, если BooleanPrefField является окончательным, вы можете просто вернуть объект этого класса в своем насмешке.

when(prefs.enabled()).thenReturn(SharedPreferencesHelper.booleanField("", false));
person alayor    schedule 18.05.2017
comment
Я пробовал, но BooleanPrefField это final. Ошибка: Cannot mock/spy class org.androidannotations.api.sharedpreferences.BooleanPrefField Mockito cannot mock/spy because: final class - person JJD; 18.05.2017
comment
И BooleanPrefField является частным пакетом, что не позволяет мне создавать экземпляр нового объекта, такого как new BooleanPrefField(true). - person JJD; 18.05.2017
comment
Вы можете попробовать использовать SharedPreferencesHelper.booleanField("", false) - person alayor; 18.05.2017
comment
Пожалуйста, объясните... просто верните объект этого класса в насмешку... Я не мог видеть, что SharedPreferencesHelper.booleanField существует как статический метод. - person JJD; 18.05.2017
comment
Я вижу, что этот класс существует в пакете org.androidannotations.annotations.sharedpreferences. Должен быть способ создать объект BooleanPrefField. javadox.com/org.androidannotations/ androidannotations-api/3.2/ - person alayor; 18.05.2017
comment
да. Я могу расширить SharedPreferencesHelper и реализовать такой метод, как public BooleanPrefField enabledPrefField() { return super.booleanField("enabled", false); }, но какой SharedPreferences объект передать конструктору? Он вызывается внутри BooleanPrefField.getOr(), как и в sharedPreferences.getBoolean(key, defaultValue);. - person JJD; 18.05.2017
comment
Вы можете передать издевательский объект SharedPreferences. - person alayor; 18.05.2017
comment
Я обновил свой вопрос с текущей реализацией. Однако при сохранении значения с помощью put насмешка снова терпит неудачу из-за ограничения final BooleanPrefField. - person JJD; 22.05.2017
comment
Я создал упрощенный образец проекта здесь. - person JJD; 22.05.2017
comment
Спасибо за ваш вклад - я, наконец, смог издеваться над final классами, используя mockito-inline. - person JJD; 22.05.2017

Вы пробовали запускать тесты с @RunWith(MockitoJUnitRunner.class) вместо MockitoAnnotations? Бегун должен автоматически инициализировать все @Mock аннотированные поля и избавиться от этого NPE.

Также, возможно, следующий ответ поможет вам в дальнейшем.

person QBrute    schedule 18.05.2017
comment
Объект prefs инициализирован. Это не проблема. NPE возникает, когда я вызываю .get для prefs.enabled(), который возвращает BooleanPrefField в случае реализации. - person JJD; 18.05.2017