Как тестировать код, который зависит от контекста?

Тестирование кода, который зависит от Context в Android, — это частая задача, особенно при разработке компонентов, работающих с ресурсами, файлами, SharedPreferences, базами данных и т.д. Поскольку Context — это системный объект, его нельзя напрямую использовать в юнит-тестах без подготовки. Ниже описаны основные подходы, которые позволяют тестировать такой код.

1. Инъекция зависимостей (Dependency Injection)

Самый универсальный способ — не создавать Context внутри класса, а передавать его снаружи, например через конструктор или сеттер.

class PreferenceHelper(private val context: Context) {
fun getValue(): String {
val prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
return prefs.getString("key", "") ?: ""
}
}

📌 Такой код можно протестировать, передав мок контекста.

2. Использование Mockito или MockK

В юнит-тестах Context часто мокается — создаётся "поддельная" версия, возвращающая нужные значения.

С использованием Mockito:

val mockContext = mock(Context::class.java)
val mockPrefs = mock(SharedPreferences::class.java)
whenever(mockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mockPrefs)
whenever(mockPrefs.getString("key", "")).thenReturn("test")
val helper = PreferenceHelper(mockContext)
assertEquals("test", helper.getValue())

📌 Так можно протестировать логику без реального Context.

3. Использование Robolectric

Для более реалистичного тестирования Android-кода в JVM можно использовать Robolectric — фреймворк, который симулирует поведение Android в обычных юнит-тестах.

@RunWith(RobolectricTestRunner::class)
class PreferenceHelperTest {
@Test
fun testSharedPreferences() {
val context = ApplicationProvider.getApplicationContext<Context>()
val helper = PreferenceHelper(context)
val prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
prefs.edit().putString("key", "value").apply()
assertEquals("value", helper.getValue())
}
}

📌 Преимущество Robolectric — доступ к реальным Android API, включая Context, Intent, Resources.

4. Использование Android Instrumented Tests

Если код плотно зависит от настоящего Context, например, работает с базой данных или файлами — используйте инструментальные тесты, выполняемые на эмуляторе или устройстве.

@RunWith(AndroidJUnit4::class)
class DbTest {
@Test
fun testDatabaseAccess() {
val context = ApplicationProvider.getApplicationContext<Context>()
val db = Room.inMemoryDatabaseBuilder(context, MyDatabase::class.java).build()
// тестируем работу с БД
}
}

📌 Такие тесты требуют подключения Android SDK и запуска через androidTest.

5. Фейковый или тестовый Context

Для простых случаев можно создать тестовый класс, реализующий часть интерфейса Context, возвращающий нужные значения.

class FakeContext : ContextWrapper(ApplicationProvider.getApplicationContext()) {
// можно переопределить только нужные методы
}

📌 Это полезно, если нельзя использовать фреймворки, но нужно протестировать часть логики.

✅ Лучшие практики

  1. Не используйте Context напрямую в бизнес-логике — выносите его наружу.

  2. Используйте интерфейсы и абстракции (SharedPreferencesProvider, ResourceProvider) — их легко подменить в тестах.

  3. Выбирайте подходящий инструмент:

    • Mockito / MockK — для юнит-тестов.

    • Robolectric — для сложных случаев в JVM.

    • Инструментальные тесты — для работы с реальной системой.

Итого

Тестировать код, зависящий от Context, можно и нужно. Это достигается через:

  • **Инъекцию зависимостей
    **
  • **Мокинг Context и его зависимостей
    **
  • **Использование Robolectric или Android-инструментальных тестов
    **

Главная идея — изолировать логику от платформенных зависимостей, делая её тестируемой и модульной.