Привет, меня зовут Николай. Моя должность в Delivery Club — QA Automation Engineer в мобильной платформенной команде. Эта статья будет о том, как мы подменяем и храним ответы бэкенда при UI-автоматизации тестирования курьерского Android-приложения. Возможно, вам нужен труднодостижимый ответ сервера в тесте, или ваши тестовые окружения часто бывают нестабильны, или вы зависите от сторонней системы? И если пока что вы не можете повлиять на это, то у вас может возникнуть желание написать функциональный тест с подмененным бэкендом. Проекты, с которыми я сталкивался, зачастую просто использовали JSON-файлы в качестве ответов, но у этого подхода есть свои недостатки, и мы рассмотрим их. Также предложу несколько альтернативных способов хранения ответов бэкенда, опишу их достоинства и недостатки. Общие моменты, связанные с написанием UI-тестов, мы почти не будем рассматривать. Немного теории Известные способы подмены бэкенда в Android-тестах Подключаем необходимые зависимости Стабы из JSON-ресурсов Стабы из Raw Strings Стабы из моделей Стабы из Key Value Стабы в произвольном стиле Сравнение способов хранения стабов Выводы Разные источники приводят разные варианты классификации тестовых дублеров. Для этой статьи подойдет самое простое деление на стабы и моки.  Test doubles (тестовые дублеры) — фиктивные объекты, заменяющие реальные в тестируемой системе. Тестовый дублер может выступать сразу в нескольких ролях. Stub (стаб) — ничего не проверяющая и зачастую упрощенная замена какого-либо объекта. Mock (мок) — замена объекта, способная отслеживать собственное состояние, в частности, количество обращений к ней, параметры вызова и прочее. В коде я решил остановиться на термине Mock, чтобы не усложнять восприятие более важных деталей при работе с MockWebServer (об инструменте расскажу далее). При этом будем иметь в виду, что на самом деле мы будем создавать Stub для наших автотестов. Инструментальные тесты — автотесты, которые выполняются на эмуляторе или на устройстве. Считается, что в инструментальных тестах использовать тестовые дублеры следует не для всего, при этом сетевую составляющую можно подменить без существенного ухудшения качества тестирования. Подчеркну, что автотесты c использованием тестовых дублеров не являются полноценной заменой e2e-тестированию. Мне известны следующие способы создания сетевых заглушек в Android-тестах: Варианты для MockWebServer, которые далее мы разберем подробнее: Хранение JSON в Assets. Хранение JSON в Kotlin-классах как Raw strings. Хранение тела ответа в Map из Key Value, сериализуемой в JSON. Хранение тела ответа в DTO модели, сериализуемой в JSON. Локальный стаб-сервер, который может работать в режиме Record and Playback (например, WireMock). Внешний стаб-сервер в инфраструктуре компании. При необходимости можно использовать кодогенерацию из Swagger (Open API). Стаб-сервер может выступать как прокси, в котором часть ответов на запрос подменяется, а часть приходит с реального бэкенда. Моки интерфейсов HTTP-клиента (например, Retrofit). Это продвинутый уровень и зачастую может потребоваться участие разработчика. Прочие библиотеки, работающие схоже с MockWebServer и WireMock. Сетевым клиентом в нашем примере будет Retrofit, а работать с JSON будем посредством kotlinx-serialization. В качестве замены сервера будет использован MockWebServer от создателей OkHttp/Retrofit. Эта библиотека имеет ряд возможностей, среди которых нам понадобится подмена ответов. Для удобного тестирования пользовательского интерфейса будем использовать Kaspresso. Первым делом добавим библиотеку в build.gradle и скорректируем AndroidManifest, чтобы получить возможность отправлять запросы по незащищенному HTTP. build.gradle  network_security_config.xml для debug сборки AndroidManifest.xml  DI-реализация сильно зависит от конкретного проекта. Она позволит подменять реальное сетевое взаимодействие фиктивным для нужд тестирования. Также для решения задачи помимо основного Application нужно будет создать класс Test Application. Ниже привожу упрощенный вариант, на который вы можете опираться при внедрении. Полный вариант можно изучить в репозитории. Создаем разные реализации интерфейса baseUrlProvider:  где для Application адрес будет реальным: а для Test Application адрес будет из локальной сети: Порт в нашем случае можно указать явно, потому что тест использует окружение с изолированной сетью.  Далее создаем наследника AndroidJUnitRunner и указываем в нем недавно созданный Test Application. А на наследника AndroidJUnitRunner в свою очередь ссылаемся в build.gradle: build.gradle  Мы будем использовать пример ответа endpoint’а, который содержит много полей, и сопоставим с endpoint'ами из реальных production-приложений. Например, он может быть таким: https://api.github.com/users/primechord/repos. Многочисленность полей поможет подсветить достоинства и недостатки рассматриваемых подходов. Dispatcher — механизм MockWebServer, позволяющий создать правила обработки запросов.  Создадим свой Dispatcher. Если будет соответствие ключу из Map, то мы будем отдавать фиктивный ответ. В MockWebServer ответ выражается через класс MockResponse. Если MockWebServer увидит запрос для Path, которого нет среди ключей, то будем отдавать код 404. В текущей реализации можно использовать ключи, которые соответствуют окончанию endpoint'а (endsWith). Привожу пример расширяемого набора стабов, который будет использован по умолчанию, чтобы начать тест с нужного экрана приложения без сетевых ошибок в значимых endpoint'ах. Также прикладываю вспомогательные функции для установки тела ответа: Ну и, конечно же, привожу базовый класс тестов: Приведенный ниже вариант с анонимным классом прост, хотя вы можете написать сколь угодно сложное условие в блоке when: А вот более лаконичный вариант с использованием утилитного кода, который можно использовать повторно в других тестах класса: Теперь добавим функцию расширения MockWebServer, которую далее будем использовать на уровне теста: Примечательно, что повторный вызов mockResponses снова установит Dispatcher, а это иногда может пригодиться при тестировании разных состояний в тесте. Вам нужно будет лишь указать измененный стаб при повторном вызове. ModelContainer — для модели, сериализуемой в строку тела ответа. KeyValueContainer — для Key-Value-объекта, сериализуемого в строку тела ответа. RawStringContainer — для тела ответа в виде уже готовой строки. AssetContainer — для тела ответа, которое хранится в Assets. AnyContainer — контейнер, который позволит создать произвольный MockResponse. Может быть полезен при тестировании негативных сценариев. Еще более лаконичный вариант в сравнении с предыдущими: Создание дополнительных тестовых случаев будет происходить с помощью полного копирования файла ресурса. При Code Review JSON-файлы нередко игнорируются. Проблематично написать документирующий комментарий. Вы также можете хранить JSON в виде строки, которая обрамляется тройными кавычками. Например, это может выглядеть так: """{ }""". Далее такая строка сериализуется в JSON.  В этом варианте вам будут доступны возможность языка String templates и возможность среды разработки Language injections.  В тестах нашего курьерского приложения способ не был опробован, но есть рабочий пример в приложенном репозитории и, пожалуй, я приведу его для полноты картины. Есть проверка синтаксиса Raw strings посредством IDE Language injections. Доступны фича String templates, которая поможет работать с датами, а также «старая добрая» String concatenation. Можно оставлять комментарии. IDE может медленно работать с файлами, которые подлежат компиляции и состоят из тысяч строк кода. Рассмотрим упрощенный пример модели из приложения, которая будет использоваться в тестах: Далее будут приведены примеры использования моделей как из приложения, так и сгенерированных для тестов. Существенное различие в том, что модели для тестов будут со значениями по умолчанию. Плагин для генерации — json-to-kotlin-class. Доступность файлов между модулями регулируется на уровне Gradle.  Если вы используете модели из приложения, то не дублируете аналогичный код в тестах. Если вы используете сгенерированные для тестов модели, то меньше завязываетесь на production-код в тестах. Опираясь на перечисленные ниже достоинства и недостатки, выбирайте предпочтительный для вас способ работы с моделями. Модель проверяется компилятором. Вам также не нужно донастраивать JSON как строку. Data class содержит значения по умолчанию. Раскрытие значимых данных теста в месте использования без необходимости указания значения каждого поля ответа. Такой код уже сложнее проигнорировать при Code Review. Появляется решение для ситуаций, когда вам нужны динамические данные в полях, например, даты. Гибкость в заполнении полей позволяет удобнее тестировать негативные сценарии. Стабы из объектов удобно документировать, и они сами могут выступать своего рода документацией. Если необходимая для стаба модель находится не в common-модуле и пока не планируется делать ее общей, то вам придется скопировать ее в модуль с тестами. Актуально для многомодульных проектов. Если модель отягощена избыточными для тестов подробностями, то вы также можете легко генерировать Data class при помощи IDE-плагина. Останется разово указать все значения по умолчанию и внести мелкие правки для типов данных, которые плагин не всегда удачно выводит, что свойственно инструментам кодогенерации. Неподготовленному человеку проще взять JSON в Charles/Fiddler, положить в Assets и не думать о проблемах долгосрочной поддержки. Ответы с большой вложенностью будут иметь много круглых скобок и, соответственно, сдвигаться линтером вправо, что в некоторых случаях может затруднять чтение кода. Вы также можете хранить JSON в структуре, которая будет состоять из Collection, Map и JsonPrimitive, и далее сериализовать ее в JSON.  В тестах нашего курьерского приложения способ не был опробован, но есть рабочий пример в приложенном репозитории и, пожалуй, я также упомяну его для полноты картины. С JsonElement из kotlinx.serialization можно работать по-разному. Я остановился на следующем варианте. Можно повторно использовать написанные объекты. Можно работать с датами. Можно оставлять комментарии. Написание стабов в предложенном стиле напоминает DSL. Времязатратно создавать и отлаживать. Также предлагаю рассмотреть способ создания стабов, позволяющий делать произвольные MockResponse. Это пригодится для тестирования негативных сценариев, в которых вам может понадобиться больше гибкости. Способ останется вне итогового сравнения.  Сравниваем подходы в соответствии с описанными выше достоинствами и недостатками: Ниже я сравнил подходы по скорости, изолированности и простоте настройки для теста. Данные для сравнения не собирались, в таблице отражено мнение автора. Предлагаемая обертка была применена для курьерского приложения Delivery Club. Она сохраняет гибкость и позволяют задавать стабы как объекты, файлы ресурсов, raw strings и произвольные MockResponse. По моему мнению, использование моделей для стабов является предпочтительным и наиболее выразительным вариантом, но, в зависимости от ситуации, вы можете выбрать и другие способы. Желающие могут ознакомиться с репозиторием, в котором есть простое одноэкранное приложение и тесты для него, демонстрирующие вышеперечисленные подходы. Благодарю за внимание. Как начать писать автотесты и не сойти с ума ​​Быстрый старт: гайд по автоматизированному тестированию для Android-разработчика. JVM Мок-сервер для автоматизации мобильного тестирования https://github.com/JorgeCastilloPrz/hiroaki https://github.com/andrzejchm/RESTMock   Инженер