В чем разница между моками и спаями в тестировании
Что проверяют
Понимаете ли вы, чем подменённая зависимость отличается от настоящего объекта под наблюдением, и умеете ли выбрать нужный инструмент. Следом часто просят показать код на Mockito или MockK и спрашивают, почему в Android-тестах вместо моков нередко пишут фейки.
Как отвечать
Начните с терминов. По классификации Мартина Фаулера мок и спай это разновидности тестовых дублёров, рядом со stub и fake. В повседневной Android-разработке под ними понимают конкретное поведение библиотек:
- мок: объект целиком пустой. В Mockito
mock<Repo>()на любой незастабленный вызов вернёт значение по умолчанию:null,0,false, пустую коллекцию. В MockKmockk<Repo>()по умолчанию строгий и броситMockKException, пока вы не опишете поведение черезevery { }или не создадите его сrelaxed = true; - спай: обёртка над настоящим объектом. Методы выполняют реальный код, пока вы не переопределите конкретный вызов. В Mockito это
spy(obj), в MockKspyk(obj); - проверка вызовов:
verifyработает и для моков, и для спаев, разница в том, что происходит при самом вызове.
Мок берут, чтобы отрезать внешнюю зависимость: сеть, базу Room, Context. Спай нужен, когда объект должен работать по-настоящему, а подменить надо один метод или посчитать вызовы. Хорошо добавить, что рекомендации Android по тестированию предпочитают фейки: FakeRepository на MutableStateFlow проверяет результат, а не порядок вызовов, и не ломается при рефакторинге.
Отдельно про Kotlin: классы по умолчанию final, и Mockito мокает их через inline mock maker, который стал поведением по умолчанию в Mockito 5. MockK с final работает сразу.
Где спотыкаются
Стабят спай через when(spy.load()): реальный метод вызывается прямо в момент стаббинга и может упасть. Для спаев нужен doReturn(x).when(spy).load(), в MockK эту проблему снимает every { }.
Не знают, что Mockito spy(obj) работает с копией объекта. Изменения исходного экземпляра спай не увидит.
Шпионят за самим тестируемым классом, чтобы заглушить его внутреннюю логику. Это признак того, что класс делает слишком много, и тест будет ломаться от любой перестановки кода.