11.4 Mocking

← 11. TDD – Automatizált tesztek

11.4 Mocking és dependency isolation

Áttekintő

Ha unit tesztet írsz, az a cél, hogy egyetlen dolgot tesztelj – a saját kódodat. De a valódi kód ritkán él légüres térben: adatbázist hív, API-t üt meg, fájlt olvas, e-mailt küld. Hogyan teszteled ilyenkor a saját logikádat anélkül, hogy minden alkalommal éles adatbázist futtatsz vagy valódi e-mailt küldesz?

Erre való a mock – egy helyettesítő objektum, ami úgy viselkedik, mintha az igazi függőség lenne, de te irányítod, hogy mit adjon vissza.

Ez az alfejezet nem a mocking eszközök mély technikai részleteiről szól. A cél: értsd meg, mikor és miért kell mockolni, és mikor ne. Ezt a tudást azonnal alkalmazhatod bármilyen nyelvben.


Részletes leírás

Miért kell egyáltalán mockolni?

Képzeld el, hogy írsz egy függvényt, ami:

  1. Lekéri a felhasználó adatait az adatbázisból
  2. Ellenőrzi, hogy a felhasználó prémium előfizető-e
  3. Ha igen, engedélyezi a funkciót

A saját logikád a 2-3. lépés. Az 1. lépés (adatbázis hívás) csak eszköz hozzá.

Ha a teszthez minden alkalommal valódi adatbázis-kapcsolatot kell nyitni:

  • A teszt lassú lesz (milliszekundumok helyett másodpercek)
  • A teszt törékeny – ha az adatbázis nem elérhető, a teszt megbukik, hiába helyes a kódod
  • A teszt nehezen reprodukálható – különböző adatok a DB-ben = különböző eredmények
  • Tesztelési adatok bepiszkítják az éles rendszert

A mock megoldja ezeket: egy kontrolált, gyors, determinisztikus helyettesítőt kapsz.


Fake, stub, mock – mi a különbség?

Ezeket sokan felváltva használják, és a különbség ritkán számít a mindennapokban. De érdemes tudni:

FogalomMi ez?Mikor használod?
FakeValódi (de egyszerűsített) implementációIn-memory adatbázis teszteléshez
StubElőre beprogramozott válaszokat ad"Ha ezt hívod, add vissza ezt"
MockStub + ellenőrzi, hogy hívták-e és hogyan"Biztosan meghívták az e-mail küldést?"

A mindennapi nyelvben: legtöbben mindent "mock"-nak hívnak. Ez rendben van. A lényeg a koncepció: valódi függőség helyett kontrolált helyettesítő.


Dependency injection – a tesztelhetőség kulcsa

Ahhoz, hogy mockolni tudj, a kódodnak fogadnia kell tudni a függőségeket kívülről – nem belülről létrehozni azokat.

Nehezen tesztelhető kód (függőség belülről):

# Python
class UserService:
    def is_premium(self, user_id):
        db = DatabaseConnection()  # itt hozza létre – nem lehet kicserélni
        user = db.find(user_id)
        return user.subscription == "premium"
// JavaScript
class UserService {
    isPremium(userId) {
        const db = new DatabaseConnection();  // nem cserélhető ki
        const user = db.find(userId);
        return user.subscription === 'premium';
    }
}

Tesztelhető kód (dependency injection):

# Python
class UserService:
    def __init__(self, db):  # kívülről kapja a függőséget
        self.db = db

    def is_premium(self, user_id):
        user = self.db.find(user_id)
        return user.subscription == "premium"

# Tesztben:
mock_db = MockDatabase()
mock_db.find.return_value = User(subscription="premium")

service = UserService(db=mock_db)
assert service.is_premium(42) == True
// JavaScript
class UserService {
    constructor(db) {  // kívülről kapja
        this.db = db;
    }

    isPremium(userId) {
        const user = this.db.find(userId);
        return user.subscription === 'premium';
    }
}

// Tesztben (Jest):
const mockDb = { find: jest.fn().mockReturnValue({ subscription: 'premium' }) };
const service = new UserService(mockDb);
expect(service.isPremium(42)).toBe(true);
// PHP (Laravel)
class UserService {
    public function __construct(private UserRepository $repo) {} // injektálva

    public function isPremium(int $userId): bool {
        $user = $this->repo->find($userId);
        return $user->subscription === 'premium';
    }
}

// Tesztben (PHPUnit + Mockery):
$mockRepo = Mockery::mock(UserRepository::class);
$mockRepo->shouldReceive('find')->with(42)->andReturn((object)['subscription' => 'premium']);

$service = new UserService($mockRepo);
$this->assertTrue($service->isPremium(42));

A minta mindig ugyanaz: a függőséget konstruktoron (vagy metóduson) keresztül kapja az osztály, nem maga hozza létre. Ettől lesz cserélhető – és tesztelhető.


Mikor kell mockolni, mikor nem?

Mockolj, ha a függőség:

  • Külső rendszert hív (adatbázis, API, fájlrendszer, e-mail küldő)
  • Lassú vagy nehezen kontrollálható
  • Side effectje van (éles adatot módosít, e-mailt küld)
  • Nem deterministikus (pl. random(), datetime.now())

Ne mockolj, ha:

  • Az a belső logika, amit tesztelni AKARSZ
  • Egyszerű, tiszta függvény más tiszta függvényekkel
  • A valódi implementáció gyors és oldalhatásmentes

Rossz irányba megy, ha minden egyes függvényhívást mockolsz, beleértve a saját belső segédfüggvényeidet is. Ilyenkor a teszt nem a viselkedést teszteli, hanem az implementáció konkrét lépéseit – és minden refactornál el fog törni.


A túlmockolás veszélye

Ez az egyik leggyakoribb hiba junioroknál: mindent mockolnak, mert úgy tűnik, ez a "helyes" megközelítés.

Tipikus tünet:

# Ez már túl sok
def test_process_order():
    mock_user_service = Mock()
    mock_inventory = Mock()
    mock_payment = Mock()
    mock_notification = Mock()
    mock_logger = Mock()
    mock_audit = Mock()
    # ... a teszt maga 3 sor, de a setup 40 sor mock

Ha a teszted több mock-kal rendelkezik, mint valódi logikával, kérdezd meg magad:

  • Tényleg unit tesztet kellene írnom ide?
  • Vagy inkább integrációs tesztre lenne szükség?
  • Esetleg a kódom túl sok felelősséget vállal magára?

A mockok nem célt, hanem eszközt jelentenek. Ha a setup kínlódás, az sokszor a tesztelési stratégia vagy a kódarchitektúra jelzése.


Életszerű példák

1. példa: E-mail küldés tesztelése

Egy regisztrációs folyamatot tesztelsz. A folyamat végén üdvözlő e-mailt küld. Nem akarod, hogy a teszt futásakor valódi e-mail menjen ki.

# Python - unittest.mock
from unittest.mock import patch, MagicMock

def test_registration_sends_welcome_email():
    with patch('myapp.email_service.send_email') as mock_send:
        register_user(name="Kovács Péter", email="peter@example.com")
        
        mock_send.assert_called_once_with(
            to="peter@example.com",
            subject="Üdvözöljük a platformon!"
        )

Mit teszteltünk? Azt, hogy a regisztrációs logika meghívja az e-mail küldést a megfelelő paraméterekkel. Nem azt, hogy az e-mail tényleg megérkezett (az az e-mail szolgáltatás tesztje, nem a miénk).


2. példa: API hívás tesztelése

// JavaScript - Jest
test('fetchUserData returns formatted user', async () => {
    global.fetch = jest.fn().mockResolvedValue({
        ok: true,
        json: async () => ({ id: 1, name: 'Kovács Péter', role: 'admin' })
    });

    const user = await fetchUserData(1);
    
    expect(user.displayName).toBe('Kovács Péter');
    expect(user.isAdmin).toBe(true);
    expect(fetch).toHaveBeenCalledWith('/api/users/1');
});

A fetch-et mockolod – a valódi API-t nem hívod meg. Így a teszt offline is fut, gyors, és az API esetleges hibáitól független.


3. példa: Mikor NEM kell mockolni

# Ezt NEM kell mockolni – tiszta függvény, nincs mellékhatás
def calculate_discount(price: float, percentage: float) -> float:
    return price * (1 - percentage / 100)

def test_calculate_discount():
    assert calculate_discount(10000, 20) == 8000
    assert calculate_discount(5000, 10) == 4500

Semmi mock, semmi setup. A függvény csak számokat vesz be és számot ad vissza – pont ezt kell tesztelni.


4. példa: Dátum/idő mockolása

# A "most" mindig más – nehéz tesztelni, hacsak nem mockolod
from unittest.mock import patch
from datetime import datetime

def test_is_business_hours():
    # Fogaszd meg az időt: legyen mindig 14:00
    with patch('myapp.datetime') as mock_datetime:
        mock_datetime.now.return_value = datetime(2026, 1, 15, 14, 0, 0)
        
        assert is_business_hours() == True

Példafeladat

Feladat: Tesztstruktúra megtervezése mock-kal

Adott a következő osztály Python-ban:

class OrderProcessor:
    def __init__(self):
        self.payment_gateway = PaymentGateway()
        self.inventory = InventoryService()
        self.notifier = EmailNotifier()

    def process(self, order):
        if not self.inventory.has_stock(order.product_id, order.quantity):
            raise InsufficientStockError()
        
        charge_result = self.payment_gateway.charge(order.user_id, order.total)
        
        if charge_result.success:
            self.inventory.reserve(order.product_id, order.quantity)
            self.notifier.send_confirmation(order.user_email)
            return {"status": "completed"}
        else:
            return {"status": "payment_failed"}

Feladatok:

  1. Azonosítsd a problémát: miért nehéz ezt jelenlegi formájában tesztelni?
  1. Refaktoráld az OrderProcessor osztályt úgy, hogy dependency injectiont használjon.
  1. Írj tesztet az alábbi három szituációra (álkód vagy valódi Python is megfelel):
  • Nincs raktáron – InsufficientStockError dobódjon
  • Sikeres fizetés – "completed" legyen a státusz és az e-mail küldjön
  • Sikertelen fizetés – "payment_failed" státusz, e-mail NE menjen
  1. Melyik függőséget mockolod? Melyiket nem? Miért?

Megoldás-vázlat (ellenőrzéshez):

class OrderProcessor:
    def __init__(self, payment_gateway, inventory, notifier):
        self.payment_gateway = payment_gateway
        self.inventory = inventory
        self.notifier = notifier
    # ... (logika változatlan)

# Teszt:
def test_process_order_no_stock():
    mock_inventory = Mock()
    mock_inventory.has_stock.return_value = False
    
    processor = OrderProcessor(
        payment_gateway=Mock(),
        inventory=mock_inventory,
        notifier=Mock()
    )
    
    with pytest.raises(InsufficientStockError):
        processor.process(sample_order)

Tesztfeladatok

1. kérdés: Melyik állítás igaz a dependency injectionről a tesztelhetőség szempontjából?

a) Dependency injection nélkül nem lehet unit tesztet írni
b) Dependency injection lehetővé teszi, hogy a tesztben kontrolált helyettesítőket adjunk át
c) Dependency injection csak production kódban hasznos, tesztben nem számít
d) Dependency injection automatikusan mock-olja a függőségeket

Helyes válasz: b


2. kérdés: Egy fejlesztő unit tesztet ír egy függvényhez, ami:

  • Vesz egy listát számokkal
  • Visszaadja az átlagukat

Kell-e mockolni bármit?

a) Igen, a lista létrehozását mockolni kell
b) Igen, a matematikai műveleteket mockolni kell
c) Nem, ez egy tiszta függvény, mock nélkül tesztelhető
d) Attól függ, melyik programozási nyelvet használja

Helyes válasz: c


3. kérdés: Melyik szituációban érdemes mockolni?

a) Egy számot formázó függvény tesztelése
b) Egy külső SMS API hívását tartalmazó kód tesztelése
c) Egy string-et nagybetűssé alakító metódus tesztelése
d) Egy tömb rendezési algoritmusának tesztelése

Helyes válasz: b


4. kérdés: Mi a legfőbb kockázata a túlmockolásnak?

a) A tesztek lassabbak lesznek
b) A tesztek az implementáció konkrét lépéseit tesztelik, nem a viselkedést – minden refactornál eltörnek
c) A mock objektumok memóriát pazarolnak
d) Nem lehet 100%-os coverage-t elérni

Helyes válasz: b


5. kérdés: Egy kolléga ezt mondja: "Mockolom a saját calculate_tax() segédfüggvényemet is, mert így igazán izolált az unit teszt."

Mi a problémája ennek a megközelítésnek?

a) Semmi, ez a helyes megközelítés
b) A calculate_tax() logika nem lesz tesztelve, és ha hibás, a tesztek nem fogják jelezni
c) Mockolni csak külső osztályokat lehet, nem saját függvényeket
d) Ez csak akkor probléma, ha a függvény async

Helyes válasz: b


6. kérdés: Melyik fogalom jelöl egy egyszerűsített, de valódi implementációt (pl. in-memory adatbázis)?

a) Mock
b) Stub
c) Fake
d) Spy

Helyes válasz: c


7. kérdés: Egy osztály belülről hozza létre az adatbázis-kapcsolatot (db = new DatabaseConnection()). Tesztelés szempontjából ez miért problémás?

a) Az adatbázis lassú, de ez a teszt minőségét nem befolyásolja
b) Nem lehet a kapcsolatot kicserélni mock-ra, ezért a teszt mindig az éles DB-t üti meg
c) Az adatbázis-kapcsolatok nem tesztelhetők unit szinten
d) Ez csak PHP-ban jelent problémát

Helyes válasz: b


8. kérdés: Mikor NEM szükséges mockolni?

a) Amikor az adatbázist használjuk
b) Amikor a rendszeróra aktuális idejét kérjük le
c) Amikor egy egyszerű, mellékhatásmentes matematikai számítást végzünk
d) Amikor e-mailt küldünk

Helyes válasz: c


9. kérdés: Mi a stub és a mock fő különbsége a mindennapokban?

a) A stub gyorsabb, a mock lassabb
b) A stub csak visszatérési értéket programoz, a mock azt is ellenőrzi, meghívták-e és hogyan
c) A stub csak Python-ban létezik, a mock más nyelveken is
d) A stub éles adatot használ, a mock fiktív adatot

Helyes válasz: b


10. kérdés: Egy fejlesztő minden egyes belső segédfüggvényt és osztályt mockol egy tesztben. A setup 50 sor, maga a teszt 5 sor. Mi ez a jel?

a) Jól megírt, izolált unit teszt
b) Valószínűleg a kód túl sok felelősséget vállal, vagy integrációs tesztre lenne inkább szükség
c) A programozási nyelv nem támogat jobb megközelítést
d) Ez a normális – komplex rendszerekhez sok mock kell

Helyes válasz: b

Scroll to Top