Как лучше мокать респонсы ретрофита?

Поделитесь, пожалуйста, как вы мокаете жсоны, когда нужно потестить работоспособность на конкретном жсоне?

Я раньше просто подкладывал .json файл вместо реального респонса в репозитории:

val mockJson: String = context.resources.openRawResource(R.raw.mock_json).bufferedReader().use { it.readText() }
val myObject = Gson().fromJson(mockJson, MyDTO::class.java)

Но тут проблема в том, что Gson не умеет String из мокового жсона конвертировать в энам.

Например, вот такой дто-класс не получится получить из жсона из-за содержащегося в нем энама:

@Serializable
data class MyDTO(
    val accountNumber: String,
    val accountBalance: String,
    val agreementType: AgreementType
)

@Serializable
enum class CardAgreementType(val value: String) {
    @SerialName("credit")
    Credit("credit"),
    @SerialName("debit")
    Debit("debit")
}

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


Ответы (1 шт):

Автор решения: Vladimir Shal'kov

Вариант 1.
Gson умеет из коробки делать мапинг данных.
Это делается, с помощью аннотации @SerializedName, пример:

val jsonString = "{\"name\":\"Юрий\",\"gender\":\"MALE\"}"
val person = Gson().fromJson(jsonString, Person::class.java)

enum class Gender {
    @SerializedName("MALE")
    MALE,
    @SerializedName("FEMALE")
    FEMALE
}

data class Person(
    val name: String,
    val gender: Gender
)

Но будте осторожны! если в ответе придёт, не то что вы ожидаете, можно получить креш:

val jsonString = "{\"name\":\"Юрий\",\"gender\":\"\"}"
val person = Gson().fromJson(jsonString, Person::class.java)
// здесь будет креш, так как пришёл null
val genderName = person.gender.name

Поэтому следует использовать nullable тип:

data class Person(
    val name: String,
    val gender: Gender?
)

Вариант 2.
Как алтернативное решение, можно сделать в ручную преобразование из string в enum.
Это может выглядеть так:

val myDTO = MyDTO(
    "1234",
    "1234",
    "credit"
)
val myDTOModel = myDTO.mapToModel()

private fun MyDTO.mapToModel(): MyDTOModel {
    return MyDTOModel(
        accountNumber = accountNumber,
        accountBalance = accountBalance,
        agreementType = CardAgreementType.getValue(agreementType)
    )
}

data class MyDTO(
    val accountNumber: String,
    val accountBalance: String,
    val agreementType: String
)

data class MyDTOModel(
    val accountNumber: String,
    val accountBalance: String,
    val agreementType: CardAgreementType
)

enum class CardAgreementType(val value: String) {
    CREDIT("credit"),
    DEBIT("debit"),
    UNKNOWN("unknown");

    companion object {

        /**
         * Получить тип enum по строковому значению
         */
        fun getValue(value: String?): CardAgreementType =
            entries.find { it.value == value } ?: UNKNOWN
    }
}

Т.е вы получает сырой ответ в MyDTO, где agreementType имеет тип string. Потом вам нужно преобразовать модель MyDTO в MyDTOModel, где переменная agreementType уже является enum. С помощью расширения MyDTO.mapToModel(), как раз и происходит преобразование.

→ Ссылка