Fake (фейк) — рабочая упрощённая реализация зависимости, которая ведёт себя как настоящий компонент, но использует in-memory хранилище или другие лёгкие механизмы вместо продакшен-инфраструктуры. В отличие от stub, fake содержит реальную бизнес-логику — сортировку, фильтрацию, агрегацию — просто без внешних эффектов. In-memory база данных вместо Room или HashMap вместо SharedPreferences — классические примеры. Подробнее — в классификации test doubles Мартина Фаулера.
Главное
Fake — это полноценная, но облегчённая реализация интерфейса, пригодная для тестирования. Термин введён Gerard Meszaros (2007) в книге «xUnit Test Patterns». В отличие от stub, который возвращает жёстко заданные ответы, fake содержит исполняемый код: он может сортировать список, фильтровать по условию, считать количество записей. Единственное отличие от продакшен-реализации — fake работает с in-memory данными и не выполняет реальных I/O-операций.
Основное преимущество — скорость. Тесты с fake выполняются за миллисекунды, потому что нет обращения к диску, сети или базе данных. In-memory HashMap работает в 100-1000 раз быстрее Room или CoreData. При этом fake проверяет реальную бизнес-логику: сортировку, фильтрацию, агрегацию — всё то, что stub не может проверить, потому что stub возвращает только то, что ему сказали. Fake даёт уверенность, что код правильно обрабатывает данные, а не просто получает предопределённый ответ.
Fake предпочтительнее Stub — если тестируемый компонент выполняет несколько операций над данными (достал, отфильтровал, отсортировал, сохранил), stub потребует настройки каждого вызова отдельно. Fake содержит логику внутри себя — тест просто вызывает методы и проверяет результат. В IT Sectr мы используем fake для всех репозиториев в unit-тестах: fake-репозиторий с HashMap покрывает 90% сценариев без настройки Mockito или MockK.
Критерий выбора — определите, что проверяет тест: состояние или взаимодействие. Если тест проверяет состояние (результат работы) и использует логику — нужен fake. Если тесту нужны только входные данные без логики — достаточно stub. Если тест проверяет факт вызова метода — нужен mock. Смешивание типов test doubles в одном тесте усложняет понимание и увеличивает хрупкость.
| Критерий | Fake | Stub | Mock |
|---|---|---|---|
| Наличие логики | Да (упрощённая) | Нет | Нет |
| Скорость | Высокая | Максимальная | Высокая |
| Проверка поведения | Косвенная | Нет | Да (verify) |
| Обслуживание | Один класс на интерфейс | Настройка под каждый тест | Настройка под каждый тест |
| Реализм | Высокий (код работает) | Низкий (жёсткие данные) | Средний |
| Риск ложных срабатываний | Низкий | Средний | Высокий (хрупкие тесты) |
Антипаттерн: Fake, который не fake — распространённая ошибка, когда разработчик называет fake-объект, который на самом деле является stub или mock. Если ваш InMemoryUserRepository не содержит логики (фильтрации, сортировки) — это не fake, а stub с in-memory хранением. Fake отличается от stub именно наличием исполняемой логики. Если fake-репозиторий просто возвращает то, что в него положили, и не обрабатывает данные — используйте mock или stub.
Практическая рекомендация — начинайте с fake для каждого репозитория или сервиса. Если fake сложнее 50 строк — разбейте на несколько классов. Если fake вообще не нужен (тест проверяет только один сценарий с фиксированными данными) — используйте stub. Если тест проверяет, что метод был вызван с определёнными параметрами — mock. Не оптимизируйте выбор заранее: напишите fake, и если он окажется избыточным, замените на stub в конкретном тесте.
Fake-репозиторий для Room — типичный пример fake на Android. Продакшен-реализация UserRepository использует Room DAO с SQLite-запросами. Fake-версия хранит данные в MutableList или HashMap и реализует те же методы: getUser(id), saveUser(user), deleteUser(id). Fake содержат логику поиска, фильтрации и сортировки — такую же, как в продакшен-репозитории, но без SQL. Это позволяет тестировать ViewModel и UseCase без настройки Room-базы.
class FakeUserRepository : UserRepository {
private val users = mutableListOf<User>()
override suspend fun getUser(id: String): User? {
return users.find { it.id == id }
}
override suspend fun saveUser(user: User) {
val index = users.indexOfFirst { it.id == user.id }
if (index >= 0) users[index] = user
else users.add(user)
}
override suspend fun search(query: String): List<User> {
return users.filter {
it.name.contains(query, ignoreCase = true)
}
}
}
Fake для Retrofit API — вместо MockWebServer (который stub, а не fake) можно создать реализацию ApiService, которая возвращает данные из in-memory коллекции. Разница: MockWebServer перехватывает HTTP и возвращает JSON, а fake-ApiService работает на уровне Kotlin-интерфейса без сериализации. Fake быстрее (нет парсинга JSON) и проще в отладке (работает в том же процессе, типизирован). Подходит для тестов, где не важна HTTP-семантика (статус-коды, заголовки).
Fake на Swift — строится через протоколы. Продакшен-класс реализует протокол с реальной логикой (CoreData, URLSession). Fake-структура реализует тот же протокол с in-memory хранилищем и упрощённой логикой. Swift — язык с value-семантикой, поэтому fake-структуры иммутабельны и безопасны в многопоточных тестах. Это даёт преимущество перед Android-аналогами: не нужно синхронизировать доступ к in-memory данным.
protocol UserRepositoryProtocol {
func getUser(id: String) async -> User?
func saveUser(user: User) async
}
final class FakeUserRepository: UserRepositoryProtocol {
private var storage: [String: User] = [:]
func getUser(id: String) async -> User? {
return storage[id]
}
func saveUser(user: User) async {
storage[user.id] = user
}
}
final class UserViewModelTests: XCTestCase {
func test_save_and_load() async {
let fake = FakeUserRepository()
let vm = UserViewModel(repository: fake)
let user = User(id: "1", name: "Alice")
await vm.saveUser(user)
let loaded = await vm.getUser(id: "1")
XCTAssertEqual(loaded?.name, "Alice")
}
}
Fake для CoreData — в iOS-проектах можно создать in-memory NSPersistentContainer, передав description.type = NSInMemoryStoreType. Это полноценный CoreData stack, но работающий в памяти. Такой fake позволяет тестировать NSFetchRequest, предикаты и сортировки без создания SQLite-файла. Скорость: тесты на in-memory CoreData выполняются в 5-10 раз быстрее, чем на дисковом аналоге. Недостаток: нужно настраивать NSManagedObjectModel каждый раз.
FakeURLProtocol — подкласс URLProtocol для перехвата сетевых запросов на iOS. Регистрируется через URLProtocol.registerClass(fakeProtocol). Внутри содержит in-memory словарь URL -> Data и возвращает данные без реального запроса. Отличие от stub: FakeURLProtocol может проверять тело запроса, заголовки и возвращать разные ответы в зависимости от входных данных. Это fake, потому что содержит логику маршрутизации запросов.
Fake как Test Fixture — выносите fake-классы в общий test-модуль (androidTest/sharedTest или TestSupport). Все тесты в проекте используют один и тот же InMemoryUserRepository. Это устраняет дублирование настройки mock-объектов в каждом тесте и гарантирует единообразное поведение. Изменение логики fake обновляет все тесты одновременно. В IT Sectr мы храним fake-классы в sharedTest/java/com/itSectr/fake/ и подключаем через implementation project(:sharedTest).
Fake с предустановленными данными — часто тестам нужен репозиторий, уже содержащий некоторые записи. Решение: фабричный метод fakeWithData(vararg items) или встроенный метод addDefaultData(). Фабрика создаёт fake, наполняет его типовыми данными и возвращает готовый к использованию объект. Это уменьшает boilerplate в тестах: вместо настройки mock-вызовов тест просто вызывает FakeUserRepository.withUsers(alice, bob).
Fake с подсчётом вызовов — иногда нужно проверить не только состояние, но и количество обращений. Fake может содержать счётчики: saveCallCount, getUserCallCount. Тест проверяет счётчик после выполнения. Это компромисс между чистым fake (проверка состояния) и mock (проверка взаимодействия). Счётчики не проверяют аргументы и последовательность вызовов — только количество. Для проверки аргументов используйте mock.
Fake с Callback — для тестирования асинхронных сценариев fake может принимать callback при каждом вызове: beforeGetUser, afterSaveUser. Это позволяет симулировать задержки, ошибки или проверять промежуточные состояния. Такой подход полезен для тестирования loading-состояний UI: fake делает паузу в 100 мс, а тест проверяет, что экран показывает лоадер. В продакшене callback отсутствует — это чисто тестовая функциональность.
Часто задаваемые вопросы
Fake содержит рабочую логику — фильтрует, сортирует, считает. Stub возвращает только заранее заданные ответы без логики. Если объект имеет ветвления (if/else, when) — это fake. Если он содержит только return values — это stub. Fake дороже в поддержке, но даёт более реалистичные тесты.
Когда fake-логика не совпадает с продакшен-логикой. Например, FakeUserRepository использует case-sensitive поиск, а продакшен — case-insensitive. Тест проходит, а в реальности баг. Решение: тестируйте fake-логику отдельно или используйте fakes только для интерфейсов с простой логикой (CRUD-операции). Для сложной логики пишите интеграционные тесты с реальной БД.
In-memory database — один из вариантов fake. Room.inMemoryDatabaseBuilder() создаёт in-memory SQLite, который ведёт себя как продакшен-БД. Это полноценный fake. Но fake может быть и на уровне репозитория (без SQL), и на уровне сети (FakeApiService). In-memory БД — частный случай fake, когда логика максимально приближена к реальной.
Да, но с осторожностью. Fake для репозитория (данные), Mock для AnalyticsTracker (верификация событий). Разделение по слоям: fake для слоя данных, mock для слоя аналитики/логирования. Не делайте один объект одновременно fake и mock — это нарушает Single Responsibility Principle и запутывает тест.
Тестируйте fake теми же тестами, что и продакшен-реализацию. Если у вас есть UserRepositoryTest, который проверяет save, get, delete — запустите его дважды: с FakeUserRepository и с RealUserRepository. Это гарантирует, что fake повторяет поведение продакшен-класса. Если fake начинает вести себя иначе — тест упадёт на обеих реализациях.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также