Skip to main content

Мокирование данных

Мок - это заглушка, т.е. данные, которые заменяют реальные. Нужно это как правило для тестов, отладки или работы без внешней зависимости, например сервера.

Работа с MockEngine

Бывают случаи, когда мобильное приложение делается одновременно с сервером, с которым оно будет работать. Из-за этого приходится делать заглушки для данных, чтобы имитировать работу сервера.

Делаем это мы с помощью возможности, предоставляемой Ktor Client-ом, под названием MockEngine. Движки нужны Ktor Client для выполнения запросов, с полным списком движков можете ознакомиться здесь. MockEngine - это тоже движок, но для тестов, который по-настоящему не отправляет никакие запросы, а просто возвращает заглушки. Он позволяет полностью воссоздать работу с настоящим сервером.

Это позволяет нам:

  • не хардкодить "тестовые" данные в репозитории или во вьюмодели
  • использовать структуры и api будущего сервера, если они уже утверждены
  • проверять, как приложение отрабатывает при разных ошибках (их тоже можно имитировать)
  • переключение между реальным сервером и тестовым осуществляется изменением всего одной строчки кода!

Детали подключения и использования можете узнать по ссылке.

Рассмотрим простой пример:

Допустим, что мы уже утвердили один запрос серверу: /info, по которому можно будет получить объект с одним единственным полем info - информация о сервере.
Значит, мы уже сейчас можем создать подходящую структуру данных для этого объекта, а также создать класс сервера, в который мы в последствии будем добавлять новые запросы:

@Serializable
data class ServerInfo(val info: String)

class Server(engine: HttpClientEngine) {
private val httpClient = HttpClient(engine) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}

suspend fun getInfo(): ServerInfo = httpClient.get("https://myAppServer/?format=json")
}

Создаем сервер и пытаемся получить информацию о нем:

fun main() {
runBlocking {
val server = Server(CIO.create())
val response = server.getInfo()
println(response.info)
}
}

Результат, который получим:

Exception in thread "main" java.nio.channels.UnresolvedAddressException
at java.base/sun.nio.ch.Net.checkAddress(Net.java:130)
at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:675)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:32)
at io.ktor.network.sockets.ConnectUtilsJvmKt.connect(ConnectUtilsJvm.kt:19)
...

Очевидно, получили ошибку, потому что такого сервера еще нет.

Чтобы протестировать логику нашего приложения, создадим тестовый mock-движок, и будем имитировать работу сервера:

val mockEngine = MockEngine { request ->
respond(
content = ByteReadChannel("""{"info":"Test server info"}"""),
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}

Теперь, создавать сервер мы будем с помощью нашего мока, а когда запустится боевой сервер - просто заменим движок.

fun main() {
runBlocking {
val server = Server(mockEngine)
val response = server.getInfo()
println(response.info)
}
}

Результат:

Test server info

Process finished with exit code 0

Реализация моков в репозитории

Если наше приложение будет работать с сервером, но мы еще не знаем ни его api, ни модели данных, которые там будут использоваться, нам следует отказаться от использования MockEngine, потому что

  • придется менять запросы, после утверждения api сервера
  • придется менять структуры данных, которые получаем от сервера

Чтобы избавить себя от этих проблем, просто возвращайте необходимые данные прямо из репозитория, без работы с сетью. Это также будет считаться моком.