Как тестировать код, который зависит от контекста?
Тестирование кода, который зависит от 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()) {
// можно переопределить только нужные методы
}
📌 Это полезно, если нельзя использовать фреймворки, но нужно протестировать часть логики.
✅ Лучшие практики
-
Не используйте Context напрямую в бизнес-логике — выносите его наружу.
-
Используйте интерфейсы и абстракции (SharedPreferencesProvider, ResourceProvider) — их легко подменить в тестах.
-
Выбирайте подходящий инструмент:
-
Mockito / MockK — для юнит-тестов.
-
Robolectric — для сложных случаев в JVM.
-
Инструментальные тесты — для работы с реальной системой.
-
Итого
Тестировать код, зависящий от Context, можно и нужно. Это достигается через:
- **Инъекцию зависимостей
** - **Мокинг Context и его зависимостей
** - **Использование Robolectric или Android-инструментальных тестов
**
Главная идея — изолировать логику от платформенных зависимостей, делая её тестируемой и модульной.