Как принцип зависимости от абстракций облегчает тестирование
Что проверяют
Понимаете ли вы, что DIP говорит о направлении зависимостей, а не призывает заводить интерфейсы везде, и как это превращается в быстрые юнит-тесты без эмулятора. Следом обычно просят показать на примере ViewModel и репозитория или спрашивают, чем DIP отличается от dependency injection.
Как отвечать
Формулировка: модули верхнего уровня не зависят от модулей нижнего, оба зависят от абстракций, а абстракции не зависят от деталей. На Android это выглядит так:
- абстракция на границе:
UserRepositoryобъявлен интерфейсом в доменном слое, аUserRepositoryImplс Retrofit и Room живёт в data-слое. ViewModel знает только интерфейс; - внедрение через конструктор: зависимость приходит параметром, граф собирает Hilt или Koin. В тесте вы просто передаёте свою реализацию, DI-фреймворк не нужен;
- фейки вместо моков:
FakeUserRepositoryнаMutableStateFlowпроще и надёжнее десяткаevery { }в MockK. Такой подход советует и официальная документация Android по тестированию; - тесты на JVM: раз ViewModel не трогает
Context, сеть и базу, её гоняют обычными юнит-тестами сkotlinx-coroutines-test(runTest,StandardTestDispatcher) и Turbine для Flow, без Robolectric; - время и потоки тоже зависимости:
ClockиCoroutineDispatcherлучше внедрять, тогда тест управляет временем, а не ждёт реальных задержек.
Итог: тест быстрый, детерминированный, проверяет логику одного класса и не падает из-за сети.
Где спотыкаются
Путают DIP и DI. Hilt доставляет зависимости, а принцип говорит о том, от чего класс зависит. Можно пользоваться Hilt и нарушать DIP, если ViewModel требует конкретный RetrofitUserApi.
Заводят интерфейс на каждый класс, включая мапперы и утилиты. Абстракция нужна на границах с внешним миром, иначе код обрастает церемониями без пользы.
Создают зависимости внутри класса: private val api = RetrofitClient.create() прямо в ViewModel. Такой класс без эмулятора или хаков с рефлексией не протестировать.