Разработка
Основы MockK
Это не просто утилита для тестирования. Это демонстрация идиоматичности дизайна Kotlin. Он использует лучшие возможности языка, такие как мощные DSL, функции расширения и первоклассную поддержку корутин, чтобы создать API, который ощущается не как инструмент, а как естественное расширение самого Kotlin.
Говоря о модульном тестировании в Android, первым делом я вспоминаю MockK. В MockK мне нравится не только его «волшебные» мок-объекты, но и элегантный API, который превращает мою ежедневную разработку в удовольствие.
И я хотел бы воспользоваться случаем и перейти от того, как использовать MockK, к тому, как работает эта магия. Итак, для начала давайте начнём с самого начала, с базового введения в то, что такое MockK и почему он стал так важен для современного тестирования Kotlin/Android.
Что такое MockK?
По сути, MockK — это «фреймворк для мок-объектов» в Kotlin. Это инструмент, позволяющий создавать и управлять «поддельными» версиями объектов (так называемыми мок-объектами), чтобы изолировать и тестировать наш код.
Но это скучное определение.
Отличительной чертой MockK является то, что он был создан с нуля разработчиками Kotlin для Kotlin. Это не просто библиотека Java, вроде Mockito, с «дружественной к Kotlin» оболочкой. Он была разработан, чтобы охватывать язык, а не просто мириться с ним.
Это фундаментальное отличие и делает фреймворк таким «волшебным». Это не просто утилита для тестирования. Это демонстрация идиоматичности дизайна Kotlin. Он использует лучшие возможности языка, такие как мощные DSL, функции расширения и первоклассную поддержку корутин, чтобы создать API, который ощущается не как инструмент, а как естественное расширение самого Kotlin.
Установка
Это определение может показаться немного многословным, поэтому давайте перейдём к конкретному примеру. Сначала просто установите зависимость в файле build.gradle (или build.gradle.kts), и всё готово:
testImplementation("io.mockk:mockk:1.14.6")
Теперь предположим, что у вас есть код и вы хотите проверить, правильно ли работает AuthManager с ILoginService:
class AuthManager(private val loginService: ILoginService) {
fun login(account: String, password: String): Boolean {
return loginService.login(account, password)
}
}
interface ILoginService {
fun login(account: String, password: String): Boolean
}
Вот что вы можете сделать:
@Test
fun `simple login test`() {
// 1. Arrange: Create a fake (mock) object
val loginService = mockk<ILoginService>()
// 2. Arrange: Define the mock's behavior
every { loginService.login(any(), any()) } returns true
// 3. Act: Inject the mock and run the code
val authManager = AuthManager(loginService)
val result = authManager.login("account", "password")
// 4. Assert: Verify the results and interactions
verify(exactly = 1) { loginService.login("account", "password") }
Assert.assertEquals(true, result)
}
Этот тест идеально соответствует классическому принципу «Arrange, Act, Assert» (3A). Теперь давайте рассмотрим каждый новый API MockK по отдельности, чтобы увидеть, как он помогает нам достичь этого.
mockk
val loginService = mockk<ILoginService>()
Вот тут-то и начинается волшебство. mockk создаёт поддельный объект (или мок) из ничего. Вам не нужно беспокоиться о предоставлении его зависимостей или о том, как вызывается его конструктор. MockK справляется с этим, манипулируя байт-кодом в рантайме, чтобы сгенерировать нужный объект.
spyk
Есть ещё один вариант — spyk. Вы можете использовать его, когда вам нужна не полная подделка, а «шпион» за настоящим объектом.
Он оборачивает реальный экземпляр объекта, позволяя использовать его реальные методы по умолчанию, сохраняя при этом возможность создания заглушек или проверки определенного поведения.
val loginService = spyk(LoginService())
Хотя это и небольшое различие, переменная loginService здесь является шпионом реального экземпляра LoginService, а не только типом интерфейса ILoginService.
every
every { loginService.login(any(), any()) } returns true
Функция every — это ядро DSL MockK. Именно здесь вы управляете поведением вашего мок-объекта при вызове одной из его функций.
В нашем примере мы используем any(), чтобы указать, что функция вернет true независимо от аргументов, переданных в функцию. Вы также можете указать более строгие условия для более корректного тестирования кода, где это применимо.
Если ваша функция — suspend функция, MockK вам поможет. Просто используйте coEvery вместо every.
Обработка вызовов без заглушек: relaxed мок-объекты
Итак, вы, вероятно, можете представить, что произойдёт, если мы вызовем функцию для мок-объекта, для которого мы не установили заглушку (или не «предоставили возвращаемое значение»): MockK не сможет предоставить ответ, что, в свою очередь, приведёт к провалу теста.
В зависимости от вашего варианта использования, вам может потребоваться более толерантный мок-объект, возвращающий значение по умолчанию (например, null или 0) вместо выдачи ошибки. И вот тут-то в игру вступает параметр relaxed:
val loginService = mockk<ILoginService>(relaxed = true)
Объединение заглушки с созданием
Мы также можем объединить создание мока и объявление его поведения в один блок, чтобы сделать код более лаконичным и читабельным.
val loginService = mockk<ILoginService> {
every { login(any(), any()) } returns true
}
verify
После этапов Arrange и Act завершается этапом Assert. В MockK это особенно эффективно, поскольку мы можем не только проверить, соответствует ли ответ функции ожидаемому, но и убедиться, что наш желаемый мокированный объект был вызван с правильными аргументами (с помощью блока verify).
verify(exactly = 1) { loginService.login("account", "password") }
mockkStatic и mockkObject
И mockk, и spyk работают для классов и интерфейсов, но если вы хотите создать фиктивные объекты или статические функции/свойства, то, поскольку при вызове функции не создается новый объект, API потребуется изменить, как показано ниже:
// source code
fun staticFunc(): Int = 1
object MyObject {
fun someFunc(): Int = 2
}
// test code
mockkStatic(::staticFunc)
mockkObject(MyObject)
every { staticFunc() } returns 2
every { MyObject.someFunc() } returns 3
unmockkStatic(::staticFunc)
unmockkObject(MyObject)
А если вам не нравится идея необходимости вызывать функцию unmockXXX, вы также можете обернуть соответствующий код в блок mockkXXX, чтобы ограничить его область действия, как показано ниже:
mockkStatic(::staticFunc) {
every { staticFunc() } returns 2
}
На сегодня всё. Надеюсь, вам понравится этот базовый курс, который станет для нас отправной точкой!
-
Аналитика магазинов2 недели назад
Мобильный рынок Ближнего Востока: исследование Bidease и Sensor Tower выявляет драйверы роста
-
Интегрированные среды разработки3 недели назад
Chad: The Brainrot IDE — дикая среда разработки с играми и развлечениями
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.45
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.46

