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 је језик са вредносном семантиком, па су 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 стек који ради у меморији. Такав 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 класе у заједнички тест модул (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. Ово омогућава симулацију кашњења, грешака или проверу међустања. Такав приступ је користан за тестирање стања учитавања UI: fake прави паузу од 100 ms, а тест проверава да ли екран приказује loader. У продукцији 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 — то крши принцип јединствене одговорности и компликује тест.
Тестирајте fake истим тестовима као и продукциону имплементацију. Ако имате UserRepositoryTest који проверава save, get, delete — покрените га два пута: са FakeUserRepository и са RealUserRepository. Ово гарантује да fake понавља понашање продукционе класе. Ако fake почне да се понаша другачије — тест ће пасти на обе имплементације.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође