Spy — co to je, Mockito.spy() a verifikace volání

Autor: IT Sectr Publikováno: 2026-04-10 Doba čtení: 9 min

Spy (špión) — testovací objekt, který obaluje reálnou instanci a zaznamenává informace o každém volání: které metody byly volány, s jakými argumenty, kolikrát. Na rozdíl od mocku, spy používá reálnou implementaci obaleného objektu — volání procházejí skutečným kódem a spy pouze zaznamenává fakta. Po provedení testu vývojář zkontroluje záznamy špióna: „Byla metoda sendAnalytics volána třikrát?”. Více — v průvodci testováním Androidu.

Hlavní body

  • Spy — objekt zaznamenávající volání metod reálné implementace bez jejího nahrazení
  • Verifikace — po testu spy umožňuje zkontrolovat, kolikrát a s jakými argumenty byla metoda volána
  • Mockito.spy() — vytváří špióna na Androidu pro reálné Java/Kotlin objekty
  • Partial mocking — spy lze kombinovat se stubem: některé metody zachytávat, jiné propouštět
  • iOS — OCMock (Objective-C) a ruční špióni přes protokoly ve Swiftu

Co je Spy a čím se liší od Mocku?

Spy — je obal kolem reálného objektu, který zachycuje všechna volání metod a zaznamenává je. Reálná logika objektu se provádí: pokud metoda ukládá data, počítá hodnotu nebo provádí požadavek — vše se děje jako obvykle. Navíc spy zaznamenává metadata: název metody, argumenty, počet volání, čas provedení. Termín je součástí klasifikace Meszaros (2007) a je podrobně popsán v článku Martina Fowlera „Mocks Aren't Stubs”.

Klíčový rozdíl od Mocku — mock zcela nahrazuje objekt testovacím stubem, všechny metody standardně nic nedělají. Spy obaluje existující objekt: všechny metody standardně fungují jako obvykle, ale zároveň jsou zaznamenávány. Tento rozdíl je zásadní: mock izoluje testovaný kód od reality, spy zachovává realitu a umožňuje ji pozorovat. Volba mezi nimi závisí na tom, co je testováno.

Kdy je Spy správnou volbou

Spy — správná volba — pokud testovaný kód upravuje stav reálného objektu a test musí jak zkontrolovat výsledek (stav), tak se ujistit, že volání byla provedena ve správném pořadí. Mock není vhodný, protože neprovádí reálnou implementaci. Stub není vhodný, protože nezaznamenává volání. Spy je jediný test double, který současně zachovává reálnou logiku a poskytuje informace o voláních.

Spy vs Mock: kdy použít špióna

Mock — úplná izolace. Pokud test nemá záviset na implementaci reálného objektu (například databáze nebo síťový klient), použijte mock. Mock zaručuje, že žádné volání nedosáhne reálné komponenty. To je bezpečné a předvídatelné. Nevýhoda: mock neprovádí reálnou logiku, takže pokud testovaný kód spoléhá na návratovou hodnotu — musí být explicitně nakonfigurována pomocí when/stub.

Spy — reálná logika + pozorování. Pokud testovaný kód interaguje s objektem, jehož logika je pro test důležitá, nejen data — použijte spy. Například AnalyticsTracker, který sbírá události a periodicky je odesílá. Test kontroluje, zda byly události přidány do bufferu a zda se po odeslání buffer vyčistil. Mock to nemůže zkontrolovat, protože neprovádí reálnou logiku trackeru.

ScénářSpyMock
Reálná logika potřebnáAnoNe (stub)
Verifikace voláníAno (počet, argumenty)Ano (počet, argumenty)
Částečné stubbováníAno (některé metody — spy, jiné — stub)Ne (všechny metody — stub)
Riziko vedlejších účinkůVysoké (reálný kód)Nulové
RychlostNižší (reálná logika)Vyšší (stuby)
ČitelnostNižší (hůře se chápe, co je reálné)Vyšší (vše explicitní)

Antivzor: spy na všechno

Spy na všechno — používat spy místo mocku pro všechny testy je chyba. Spy provádí reálný kód, který může mít vedlejší účinky: zápis do souboru, odesílání HTTP, změna globálního stavu. Pokud testovaný modul zavolá metodu spy-objektu, která provádí HTTP požadavek, test se stává integračním testem, nikoli unit testem. Pravidlo: pokud spy obaluje objekt s I/O operacemi — to již není unit test. Použijte mock pro izolaci I/O, spy — pouze pro in-memory objekty bez vnějších účinků.

Mockito.spy() a spy v MockK na Androidu

Mockito.spy() — klasický způsob vytvoření špióna v Java/Kotlin projektech. spy() přijímá reálný objekt a vrací obal. Všechna volání jsou standardně delegována na reálný objekt a výsledky jsou zaznamenávány. Po provedení testu lze pomocí verify() zkontrolovat počet volání a argumenty. Pro metody, které mají vracet testovací data, se používá doReturn/when — nazývá se to „částečné stubbování” (partial mocking).

kotlin
class AnalyticsReporterTest {

    private val realTracker = AnalyticsTracker()
    private val spyTracker = Mockito.spy(realTracker)

    fun test_event_tracked() {
        val event = AnalyticsEvent("login")
        spyTracker.track(event)

        Mockito.verify(spyTracker).track(event)
        assertEquals(1, spyTracker.getBufferedCount())
    }

    fun test_track_with_exception() {
        Mockito.doThrow(RuntimeException("network"))
            .when(spyTracker).flush()

        spyTracker.track(AnalyticsEvent("login"))
        assertTrue(spyTracker.hasPendingEvents())
    }
}

MockK.spyk() — alternativa pro Kotlin projekty s lepší podporou pro korutiny a sealed třídy. MockK.spyk() vytváří špióna, analogicky k Mockito.spy(). Podporuje coVerify pro suspend funkce a every pro částečné stubbování. Na rozdíl od Mockita, MockK nepodporuje spy pro final třídy (všechny třídy v Kotlinu jsou standardně final) — je třeba třídu otevřít (open) nebo použít rozhraní.

kotlin
class LoginUseCaseTest {

    private val realRepo = UserRepository()
    private val spyRepo = spyk(realRepo)

    private val useCase = LoginUseCase(spyRepo)

    fun test_login_calls_save() = runTest {
        every { spyRepo.getUser(any()) } returns User("test")

        val result = useCase.login("test", "pass")

        coVerify { spyRepo.saveLoginTime(any()) }
        assertTrue(result.isSuccess)
    }
}

Částečné stubbování přes spy

Částečné stubbování — mocná, ale nebezpečná technika. Můžete vytvořit spy objektu a přepsat (stub) pouze některé metody, zatímco ostatní ponechat reálné. Příklad: spy-repozitář, jehož getUser() vrací testovací data a saveUser() skutečně ukládá do in-memory seznamu. To umožňuje kombinovat výhody stubů (kontrolovaná data) a spy (reálná logika). Nevýhoda: složitost čtení testu — není zřejmé, které metody jsou reálné a které jsou stub.

Implementace Spy na iOS s OCMock a protokoly

OCMock pro Objective-C — knihovna podporující vytváření spy-objektů přes niceMock. OCMock zachycuje volání metod pomocí Objective-C runtime a zaznamenává je. Po provedení testu je volána verify. OCMock podporuje spy pro libovolný objekt (v Objective-C jsou všechny metody dynamické), což poskytuje výhodu oproti Swiftu, kde je spy možný pouze přes protokoly.

objective-c
// Vytvoření spy pro reálný objekt
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];

// Provedení testu
[spy trackEvent:@"login"];

// Verifikace
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);

Swift protocol-based spy — ve Swiftu není Objective-C runtime reflexe, proto se spy vytváří ručně. Testovací struktura implementuje protokol a interně volá reálný objekt, současně zaznamenávajíc volání. To je více kódu, ale plně kontrolovatelné a typově bezpečné. Ruční špióni nevyžadují externí knihovny a nepoužívají runtime — vše je kontrolováno ve fázi kompilace.

swift
protocol AnalyticsProtocol {
    func trackEvent(name: String)
}

final class SpyAnalytics: AnalyticsProtocol {
    private let real: AnalyticsProtocol
    private var events: [String] = []

    init(real: AnalyticsProtocol) {
        self.real = real
    }

    func trackEvent(name: String) {
        events.append(name)
        real.trackEvent(name: name)
    }

    func verifyTracked(name: String) -> Bool {
        return events.contains(name)
    }
}

Kdy použít OCMock vs ruční spy — pro Objective-C kód použijte OCMock (méně boilerplate). Pro Swift — preferováni jsou ruční špióni přes protokoly. Ruční spy poskytuje plnou kontrolu nad zaznamenáváním volání, nevyžaduje reflexi a pracuje s hodnotovými typy (struct). Jedinou nevýhodou je nutnost udržovat kód spy třídy synchronizovaný s protokolem při přidávání nových metod.

Typické scénáře použití Spy

Kontrola analytiky — nejčastější scénář použití spy. V produkčním kódu jsou volání analytiky rozptýlena po celé aplikaci: login, logout, purchase, error. Test vytvoří spy-obal pro AnalyticsTracker, provede scénář (přihlášení, prohlížení produktu, přidání do košíku, nákup) a zkontroluje, zda byly všechny potřebné události odeslány ve správném pořadí. Mock není vhodný, protože AnalyticsTracker obsahuje logiku bufferování a odesílání.

Časovače a plánovače — testování kódu používajícího Handler (Android) nebo Timer (iOS) je kvůli reálnému času obtížné. Spy-obal pro Scheduler zaznamenává, které úkoly byly naplánovány a s jakým zpožděním. Test vytvoří spy reálného Handleru, provede akci a zkontroluje, zda byl Handler.postDelayed(runnable, delay) volán se správným zpožděním. Reálný úkol se přitom neprovádí — spy zachycuje a zaznamenává volání.

Logování a debug informace — v produkci mohou být logy vypnuté nebo zapisované do souboru. Spy-obal pro Logger zaznamenává všechny zprávy do in-memory seznamu, který test po provedení zkontroluje. To umožňuje zkontrolovat, zda se při chybě zapisuje správná zpráva, bez zaneřádění konzole. Ruční špióni pro Logger jsou zvláště užiteční na iOS, kde OSLog nemá testovací API.

Kontrola pořadí volání — některé scénáře vyžadují striktní pořadí operací: otevřít spojení, odeslat data, zavřít spojení. Mockito umožňuje kontrolu pořadí pomocí InOrder.verify(). Spy dělá totéž, ale zachovává reálné provedení. Pokud je důležité nejen pořadí, ale také výsledek každého kroku (spojení se skutečně otevřelo) — použijte spy, ne mock.

Často kladené otázky

Spy vs Mock: jaký je hlavní rozdíl?

Spy obaluje reálný objekt a provádí jeho logiku, navíc zaznamenávajíc volání. Mock zcela nahrazuje objekt stubem — neprovádí se žádná reálná logika. Spy zachovává chování, mock ne. Vyberte spy, když je důležitá reálná práce objektu; vyberte mock, když je třeba test izolovat od externí závislosti.

Kdy je Spy špatnou volbou?

Když spy-obal vede k reálným I/O operacím. Pokud spy obaluje objekt, který zapisuje do souboru, odesílá HTTP nebo čte z disku — test přestává být unit testem. Druhý případ: test kontroluje pouze návratovou hodnotu bez zájmu o volání — zde stačí stub a spy je nadbytečný. Třetí: kód spoléhá na vnitřní stav spy — to je křehký test.

Podporuje MockK spy?

Ano, pomocí spyk() — analogicky k Mockito.spy(). MockK.spyk() vytváří špióna kolem reálného objektu, podporuje every pro částečné stubbování a coVerify/coroutinesVerify pro suspend funkce. Omezení: nefunguje s final třídami (je potřeba open nebo rozhraní). Pro Java třídy MockK také podporuje spyk(), ale vyžaduje anotaci @MockKJvmInline.

Lze vytvořit Spy z Mocku?

Technicky — ne. Mock je stub, který neobsahuje reálnou implementaci. Spy podle definice obaluje reálný objekt. V Mockitu nelze mock přeměnit na spy. Ale opak je možný: vytvořte spy a přepište část metod pomocí doReturn/when (partial mocking). To poskytuje chování podobné mocku pro vybrané metody spy-objektu.

Spy ve Swiftu — nutně přes protokol?

Nutně. Ve Swiftu neexistuje dynamické proxy jako v Java/Kotlin. Pro vytvoření spy je potřeba protokol, který implementuje jak produkční třída, tak spy třída. Swift-protocol-based spy je ruční implementace, která přijímá reálný objekt, deleguje mu volání a zaznamenává metadata. Alternativa: knihovna Cuckoo, která generuje spy třídy pomocí SourceKit.

Shrnutí

  • Spy — obal kolem reálného objektu zaznamenávající všechna volání metod bez nahrazení logiky
  • Rozdíl od Mocku — spy provádí reálný kód, mock jej nahrazuje stubem
  • Mockito.spy() — pro Java/Kotlin, obaluje reálný objekt s možností verify a partial mock
  • MockK.spyk() — Kotlin analog s podporou korutin, sealed tříd a coVerify
  • iOS — OCMock pro Objective-C, ruční protocol-based spy pro Swift
  • Hlavní scénář — kontrola analytiky, časovačů, logů a pořadí volání
  • Pozor — spy s I/O operacemi mění unit test na integrační test

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také