Stub (стаб, замена) — тестни објекат који враћа унапред дефинисане одговоре на позиве метода уместо стварне имплементације. У мобилном развоју, стабови изолују тестирани модул од мрежних захтева, базе података и система датотека, омогућавајући проверу логике без подешавања окружења. За разлику од mock, stub не проверава понашање — он само обезбеђује податке. Детаљније у чланку Martin Fowler-a о test doubles.
Главно
Stub — је објекат-замена који замењује стварну зависност у тесту и враћа унапред задате вредности на одређене позиве. Термин је уведен у класификацији Gerard Meszaros (2007) у књизи „xUnit Test Patterns". Stub припада категорији test doubles — објеката који замењују стварне компоненте током тестирања. Основна сврха стаба је да тестираном блоку обезбеди предвидљиве податке, уклањајући несигурност спољних система.
Принцип рада — тест конфигурише стаб пре извршења: „када метод getUsers() буде позван, врати ову листу корисника". Stub не садржи пословну логику, не проверава редослед позива и не бележи историју приступа. Он једноставно стоји на месту стварне компоненте и даје оно што му је речено. У контексту Android тестирања то значи да OkHttp клијент не прави стварни захтев ка серверу, већ добија одговор из MockWebServer-а конфигурисаног као стаб.
Када користити — стабови су оптимални за тестирање UI слоја (ViewModel, Presenter) и пословне логике (UseCase, Interactor), где треба проверити реакцију на одређене податке: листа празна, сервер је вратио грешку 500, токен је истекао. Сваки случај где тест захтева одређено улазно стање — задатак је за стаб. За сваки тестни сценариј креира се своја конфигурација стаба, што чини тестове читљивим и предвидљивим.
Gerard Meszaros (2007) у књизи „xUnit Test Patterns" је издвојио пет типова test doubles: dummy, stub, spy, mock, fake. Сваки тип решава свој задатак. Dummy — прослеђује се, али се не користи. Stub — враћа податке. Spy — бележи позиве. Mock — проверава понашање. Fake — садржи поједностављену логику. Разумевање ове класификације помаже програмеру да изабере прави алат за сваки тестни сценариј.
Мрежни захтеви — најчешћи сценариј примене стабова. Апликација шаље HTTP позиве ка API-ју, а у тесту треба проверити реакцију на различите одговоре: успешан JSON, грешка 401 (неовлашћен), тајмаут, празан низ. MockWebServer (OkHttp) на Android-у и URLProtocol (iOS) делују као стабови, враћајући унапред дефинисане HTTP одговоре без стварне везе са сервером. Ово убрзава тестове са секунди на милисекунде.
База података — Room (Android) и CoreData (iOS) имају in-memory варијанте, али њихово подешавање ипак захтева време. Stab уместо репозиторијума враћа унапред припремљене листе Entity, не дирајући базу података. Ово је посебно ефикасно за тестирање ViewModel-а, где треба проверити сортирање, филтрирање или трансформацију података. Тест се извршава за милисекунде без обзира на количину података.
Системски сервиси — LocationManager, SensorManager, SharedPreferences захтевају прави уређај или емулатор. Stab за LocationProvider враћа задате координате, за SensorManager — фиксне вредности акцелерометра. На iOS-у аналог је CLLocationManager са тестном имплементацијом делегата. Без стабова такви тестови захтевају физички уређај са одређеним условима.
Систем датотека и кеш — учитавање слика, кеширање одговора, рад са конфигурационим датотекама — све ове операције зависе од стања диска. Stab за FileManager или ImageCache враћа успех/грешку без читања стварних датотека. Ово елиминише лажне падове тестова због неусаглашености путања или дозвола на различитим машинама програмера.
Подела одговорности — три типа test doubles решавају различите задатке. Stub: „дај ми податке". Mock: „провери да ли су ме позвали". Fake: „радим као прави, само једноставније". Разлика је критична за читљивост тестова: ако тест користи mock тамо где је потребан stub, преоптерећен је verify позивима који нису повезани са тестираним сценаријем.
| Карактеристика | Stub | Mock | Fake |
|---|---|---|---|
| Намена | Обезбедити податке | Проверити интеракцију | Поједностављена имплементација |
| Логика | Нема | Нема | Има (али поједностављена) |
| Верификација | Нема | Има (verify) | Индиректна (кроз стање) |
| Флексибилност | Ниска — чврсти одговори | Средња | Висока — логика се прилагођава |
| Брзина | Максимална | Висока | Средња |
| Пример | MockWebServer враћа JSON | Mockito.verify(repository).save() | InMemoryRepository са HashMap |
Практично правило — ако тест проверава које податке је тестирана компонента добила — користите stub. Ако тест проверава да ли је компонента позвала метод зависности са исправним аргументима — користите mock. Ако само желите да замените базу података хеш табелом — то је fake. Мешање типова у једном тесту чини га крхким: при промени имплементације мораћете да препишете и stub и verify логику.
Stub са verify — честа грешка када програмер конфигурише stub, а затим дода verify(stub).method(). Stub по дефиницији не треба да се верификује — за верификацију постоји mock. Ако треба да проверите да је метод позван са одређеним аргументима, користите Mockito.mock() уместо Mockito.stub(). Ово раздвајање одржава намеру теста јасном за друге програмере.
MockWebServer — OkHttp библиотека за креирање HTTP стабова на Android-у и JVM. Она покреће локални HTTP сервер на одређеном порту који пресреће захтеве OkHttp клијента и враћа унапред дефинисане одговоре. Подешавање траје три линије: креирати сервер, додати одговор у ред (enqueue), покренути. Тест може секвенцијално да дода више одговора у ред за сценарије са пагинацијом или поновних покушаја.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — алтернатива за Mockito за Kotlin са first-class подршком за корутине, extension функције и sealed класе. Стабови у MockK се креирају кроз coEvery (за suspend функције) и every (за обичне). За разлику од MockWebServer-а, MockK замењује појединачне методе-зависности, а не цео HTTP слој. Ово је згодно за unit тестове UseCase или Interactor-а, где су зависности апстракције репозиторијума.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Best practice — за интеграционе тестове користите MockWebServer (пресреће прави HTTP), за unit тестове — MockK (замењује интерфејсе). Немојте замењивати оно што не тестирате: ако тест проверава Repository, немојте замењивати OkHttp клијента унутар њега — користите прави MockWebServer на нивоу HTTP-а. Ово правило одржава тестове релевантним и смањује крхкост при рефакторисању.
Swift протоколи као стабови — у iOS-native приступу стаб се имплементира кроз подметање тестне структуре која одговара протоколу зависности. Уместо правог NetworkService, тест добија StubNetworkService који враћа фиксне податке. Swift је језик са статичким типовима, па стаб мора да одговара истом протоколу као и прави сервис. Компајлер гарантује да стаб имплементира све потребне методе.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock за Objective-C — библиотека за креирање стабова и мокова у legacy iOS пројектима. OCMock подржава stub методе са аргументима и повратним вредностима. Савремени пројекти у Swift-у преферирају protocol-based приступ са ручним стабовима — ово даје контролу над сваким методом и не захтева спољне зависности. OCMock остаје опција за пројекте где протоколизација свих зависности није економски оправдана.
URLProtocol за HTTP стабове — системски механизам iOS-а за пресретање мрежних захтева кроз подкласу URLProtocol. Тест региструје прилагођени URLProtocol који пресреће URLSession и враћа stub одговоре. Предност у односу на ручне стабове: не треба мењати архитектуру апликације — URLSession остаје прави, али се подаци замењују на нивоу протокола. Недостатак: теже се дебагује него експлицитни stub сервис.
Често постављана питања
Stub враћа унапред дефинисане податке и не проверава чињеницу позива. Mock додатно верификује да је метод позван са исправним аргументима (verify). Stub одговара на питање „шта вратити", Mock — на питање „да ли је позван". Користите stub за проверу стања, mock — за проверу интеракције.
Fake је потребан када тесту треба радна (макар и поједностављена) имплементација — на пример, in-memory база података уместо Room-а. Stub је погодан за појединачне сценарије са унапред дефинисаним подацима. Ако понављате исти stub у 10 тестова — највероватније вам треба Fake. Fake смањује дуплирање јер логика живи у једној класи.
На Android-у — MockK за Kotlin објекте (object) подржава mockkObject(), укључујући статичке методе Java класа кроз mockkStatic(). На iOS-у — статички методи Swift-а се не стабују директно; користите протоколе и DI да замените static позив методом инстанце протокола. Статички стабови су технички дуг и треба их избегавати у новом коду.
Користите MockWebServer (OkHttp) — ради као локални HTTP сервер који додаје одговоре у ред (enqueue). За Retrofit је довољно променити базни URL на localhost:8080. За Ktor користите MockEngine — уграђени механизам за замену HttpStatement. Оба приступа раде без правог интернета и дају потпуну контролу над статусним кодом, телом и заглављима одговора.
Spy обавија прави објекат и бележи позиве, док Stub у потпуности замењује објекат фиксним одговорима. Spy омогућава делимично коришћење стварне имплементације (остали методи раде као и раније), а stub — не. Ако треба да проверите да је метод позван, али део логике треба да се изврши — користите spy, а не stub.
Завршни преглед
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође