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:
- Lekéri a felhasználó adatait az adatbázisból
- Ellenőrzi, hogy a felhasználó prémium előfizető-e
- 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:
| Fogalom | Mi ez? | Mikor használod? |
|---|---|---|
| Fake | Valódi (de egyszerűsített) implementáció | In-memory adatbázis teszteléshez |
| Stub | Előre beprogramozott válaszokat ad | "Ha ezt hívod, add vissza ezt" |
| Mock | Stub + 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:
- Azonosítsd a problémát: miért nehéz ezt jelenlegi formájában tesztelni?
- Refaktoráld az
OrderProcessorosztályt úgy, hogy dependency injectiont használjon.
- Írj tesztet az alábbi három szituációra (álkód vagy valódi Python is megfelel):
- Nincs raktáron –
InsufficientStockErrordobó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
- 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