Почему getSharedPreferences возвращает null при модульном тестировании?

Мои классы написаны на Котлине, а вот мой SharedPreferenceHandler

   class SharedPreferenceHandler(sharedPrefs: SharedPreferences) {

        companion object {
            var mInstance: SharedPreferenceHandler = SharedPreferenceHandler(getPrefs())

            private fun getPrefs(): SharedPreferences {
                return Application.mInstance.getSharedPreferences(
                        "myApp", Context.MODE_PRIVATE)
            }

            fun getInstance(): SharedPreferenceHandler {
                return mInstance
            }
        }

        private var sharedPreferences = sharedPrefs

        var accessToken: String?
            get() = sharedPreferences.getString(SharedPreference.ACCESS_TOKEN.name, null)
            set(token) = sharedPreferences.edit().putString(SharedPreference.ACCESS_TOKEN.name, token).apply()
}

Вот метод, вызываемый в презентаторе:

 override fun reload(vm: ViewModel) {
        super.updateViewModel(vm) {
           //some stuffs
        }
    }

Вот мой метод тестирования:

@Test
public void reload() {
    when(SharedPreferenceHandler.Companion.getMInstance().getAccessToken()).thenReturn("234234234234234");

    presenter.reload(viewModel);
}

В обработчике из super.updateViewModel (vm) я вызываю SharedPreferenceHandler.mInstance.accessToken !!)

Вот что выброшено:

Caused by: java.lang.IllegalStateException: Application.mInstanc…m", Context.MODE_PRIVATE) must not be null at com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.getPrefs(SharedPreferenceHandler.kt:18) at com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.access$getPrefs(SharedPreferenceHandler.kt:14) at com.zuum.zuumapp.preferences.SharedPreferenceHandler.(SharedPreferenceHandler.kt:15)

Я хочу получить accessToken, вызвав SharedPreferenceHandler.mInstance.accessToken !! в моем тестовом классе.

Можно ли получить это в моем тестовом методе?

Вы проводите тесты с роботом?

Cililing 29.05.2018 18:06

Нет, я использую PowerMock для некоторых моков

azsoftco 29.05.2018 19:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
1 280
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не можете использовать Android SharedPreferences в модульном тесте, но вы можете смоделировать вызов своего метода следующим образом:

Mockito.`when`(SharedPreferenceHandler.mInstance.accessToken).thenReturn("token ")

И верните то, что вам нужно.

Ответ принят как подходящий

Вы не должны тестировать свой код таким образом. Вы должны создать интерфейс для класса, над которым хотите имитировать:

interface MySharedPreferences {
    fun getAccessToken(): String
}

Пусть ваш SharedPreferencesHandler реализует этот интерфейс. Затем в вашем презентаторе (или другом классе, который вы хотите протестировать) вставьте зависимости (например, с помощью конструктора или фреймворка, такого как Dagger / Kodein) в ваш объект. Тогда есть возможность легко смоделировать этот интерфейс. Я предполагаю, что в @Before вы создадите класс, который вы тестируете, а затем просто передайте в качестве параметра свой издеваемый SharedPreferencesHandler.

Тестирование со статическими зависимостями возможно, но довольно сложно (и многие люди считают статические зависимости антипаттерном). Как это сделать, описано здесь: Как выполнить модульное тестирование Android и имитировать статический метод

Пример:

 class MyPresenter(val sp: MySharedPreferences) {
     /* some code here */
     fun validateToken() {
         if (sp.getAccessToken() == "") throw new Exception()
     }
 }  

Как вы видите, sp вводится в этот класс как параметр. Обычно представления / докладчики и т. д. Создаются не непосредственно в коде, а с помощью инфраструктуры DI (например, Dagger или Kodein). В любом случае, статические зависимости проверить непросто. Внедренные зависимости интерфейса можно смоделировать, и вы работаете не с объектом, а с поведением (так что это более высокий уровень абстракции). Итак, теперь в вашем тесте все, что вам нужно сделать, это:

class MyTest() {

    @Mock lateinit var sharedPreferencesMock: MySharedPreferences
    lateinit var instance: MyPresenter

    @Before
    fun setUp() {
        instance = MyPresenter(sharedPreferencesMock)
    }

    @Test
    fun testSomething() {
        `when`(sharedPreferencesMock.getAccessToken()).thenReturn("myAccessToken")
        /* here is your test body */
    }
} 

Можете привести пример с интерфейсом?

azsoftco 30.05.2018 13:24

Пример с небольшими пояснениями.

Cililing 30.05.2018 14:01

«MySharedPreferences» - это просто интерфейс?

azsoftco 30.05.2018 15:08

Да. Ваш внедренный объект должен быть экземпляром интерфейса. Ваш класс handler должен реализовывать этот интерфейс - когда вы создаете MyPresenter, вы передаете экземпляр класса, реализующего этот интерфейс.

Cililing 30.05.2018 15:12

Другие вопросы по теме