pytest-mock instellen
Een virtuele omgeving voor je project maken is een best practice om afhankelijkheden te isoleren. Heb je nog geen nieuwe omgeving ingesteld, dan kun je er een met conda maken met de volgende commando’s:
# create a conda environment
conda create --name yourenvname python=3.11
# activate conda environment
conda activate yourenvname
pytest en pytest-mock installeren
Installeer pytest en pytest-mock rechtstreeks in je omgeving met pip of conda:
pip install pytest
pip install pytest-mock
Installatie bevestigen
Controleer na installatie van pytest en pytest-mock of alles gelukt is met het volgende commando:
pytest --version
Basis van mocken met pytest-mock
Je kunt mocken zien als het maken van een "dummy"-versie van een component die echt gedrag nabootst.
Het is vooral handig wanneer je wilt testen hoe functies of klassen interageren met externe componenten zoals databases of externe API’s.
Introductie tot mocken
Een mockobject simuleert het gedrag van een echt object. Het wordt vaak gebruikt in unittests om componenten te isoleren en hun gedrag te testen zonder afhankelijke code uit te voeren.
Je kunt bijvoorbeeld een mockobject gebruiken om een databaseaanroep te simuleren zonder daadwerkelijk met de database te verbinden.
Mocken is vooral nuttig wanneer het echte object:
- Moeilijk te maken of te configureren is.
- Tijdrovend is om te gebruiken (bijv. toegang tot een externe database).
- Neveneffecten heeft die je tijdens het testen wilt vermijden (bijv. e-mails verzenden, kosten maken).
De mocker-fixture gebruiken
pytest-mock biedt een mocker-fixture, waarmee je eenvoudig mockobjecten kunt maken en beheren. De mocker-fixture is krachtig en integreert naadloos in je pytest-tests.
💡Wat zijn fixtures?In Python zijn fixtures herbruikbare componenten die resources opzetten en afbreken die tests nodig hebben, zoals databases of bestanden. Ze zorgen voor consistentie, verminderen duplicatie en vereenvoudigen de testopzet. |
Laten we een voorbeeld bekijken:
def fetch_weather_data(api_client):
response = api_client.get("https://api.weather.com/data")
if response.status_code == 200:
return response.json()
else:
return None
De functie fetch_weather_data is afhankelijk van een externe weer-API om data op te halen, maar het aanroepen van de API tijdens tests kan onnodige kosten met zich meebrengen.
Om dit te voorkomen kun je mocken gebruiken om het gedrag van de API in je tests te simuleren, zodat de applicatie wordt getest zonder externe calls. Zo helpt mocken:
def test_fetch_weather_data(mocker):
# Create a mock API client
mock_api_client = mocker.Mock()
# Mock the response of the API client
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"temperature": "20C", "condition": "Sunny"}
# Set the mock API client to return the mocked response
mock_api_client.get.return_value = mock_response
# Call the function with the mock API client
result = fetch_weather_data(mock_api_client)
# Assert that the correct data is returned
assert result == {"temperature": "20C", "condition": "Sunny"}
mock_api_client.get.assert_called_once_with("https://api.weather.com/data")
In test_fetch_weather_data() gebruiken we de mocker-fixture om een mock-API-client mock_api_client en een mock_response te maken. De mockrespons is geconfigureerd om een echte API-call na te bootsen door een statuscode 200 en JSON-gegevens met weersinformatie terug te geven.
De get-methode van de mock-API-client is ingesteld om deze mockrespons terug te geven. Wanneer fetch_weather_data() getest wordt, communiceert het met de mock-API-client in plaats van een echte API-call te doen.
Deze aanpak zorgt ervoor dat de functie zich gedraagt zoals verwacht, terwijl de tests snel, kostenefficiënt en onafhankelijk van externe systemen blijven.
De assert aan het einde controleert of de geretourneerde data overeenkomt met de verwachte output.
De laatste regel, mock_api_client.get.assert_called_once_with("https://api.weather.com/data"), controleert specifiek dat de get-methode van de mock-API-client exact één keer en met het juiste argument is aangeroepen. Dit helpt te bevestigen dat de functie op de verwachte manier met de API interageert en voegt een extra laag validatie toe aan de test.
Veelvoorkomende use-cases voor pytest-mock
Mocken is nuttig in verschillende scenario’s, waaronder unittests waarin je specifieke functies of componenten wilt isoleren. Laten we enkele veelvoorkomende use-cases bekijken:
1. Functies of methoden van klassen mocken
Soms wil je een functie testen die afhankelijk is van andere functies of methoden, maar wil je hun gedrag tijdens het testen kunnen controleren. Met mocken kun je die afhankelijkheden vervangen door vooraf gedefinieerd gedrag:
# production code
def calculate_discount(price, discount_provider):
discount = discount_provider.get_discount()
return price - (price * discount / 100)
# test code
def test_calculate_discount(mocker):
# Mock the get_discount method
mock_discount_provider = mocker.Mock()
mock_discount_provider.get_discount.return_value = 10 # Mocked discount value
# Call the function with the mocked dependency
result = calculate_discount(100, mock_discount_provider)
# Assert the calculated discount is correct
assert result == 90
mock_discount_provider.get_discount.assert_called_once()
In deze test wordt de methode get_discount() van de discount_provider gemockt om een vooraf gedefinieerde waarde (10%) terug te geven. Dit isoleert de functie calculate_discount() van de daadwerkelijke implementatie van discount_provider.
2. Tijdafhankelijke code mocken
Bij het testen van functies die tijd of vertragingen bevatten, helpt het mocken van tijdgerelateerde methoden om wachten te vermijden of echte tijd te manipuleren:
# production code
import time
def long_running_task():
time.sleep(5) # Simulate a long delay
return "Task Complete"
# test code
def test_long_running_task(mocker):
# Mock the sleep function in the time module
mocker.patch("time.sleep", return_value=None)
# Call the function
result = long_running_task()
# Assert the result is correct
assert result == "Task Complete"
Hier wordt time.sleep gepatcht om de daadwerkelijke vertraging tijdens het testen te omzeilen. Zo draait de test snel zonder 5 seconden te hoeven wachten.
3. Attributen van objecten mocken
Soms wil je het gedrag van attributen of properties van een object mocken om tijdens het testen verschillende toestanden te simuleren:
# production code
class User:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def is_adult(self):
return self.age >= 18
# test code
def test_user_is_adult(mocker):
# Create a User object
user = User(name="Alice", age=17)
# Mock the is_adult property
mocker.patch.object(User, "is_adult", new_callable=mocker.PropertyMock, return_value=True)
# Assert the mocked property value
assert user.is_adult is True
In dit voorbeeld is de property is_adult() van de klasse User gemockt om True te retourneren, ongeacht de werkelijke leeftijd. Dit is handig om scenario’s te testen die afhankelijk zijn van de toestand van objecten.
Geavanceerde mockingtechnieken met pytest-mock
Als je je comfortabel voelt met de basis, biedt pytest-mock geavanceerde mogelijkheden voor complexere scenario’s.
1. Side effects mocken
Mocks kunnen meer doen dan alleen returnwaarden—ze kunnen ook gedrag nabootsen zoals het genereren van exceptions of het dynamisch wijzigen van returnwaarden. Dit doe je door “side effects” toe te voegen aan mocks:
import pytest
# production code
def process_payment(payment_gateway, amount):
response = payment_gateway.charge(amount)
if response == "Success":
return "Payment processed successfully"
else:
raise ValueError("Payment failed")
# test code
def test_process_payment_with_side_effects(mocker):
# Mock the charge method of the payment gateway
mock_payment_gateway = mocker.Mock()
# Add side effects: Success on first call, raise exception on second call
mock_payment_gateway.charge.side_effect = ["Success", ValueError("Insufficient funds")]
# Test successful payment
assert process_payment(mock_payment_gateway, 100) == "Payment processed successfully"
# Test payment failure
with pytest.raises(ValueError, match="Insufficient funds"):
process_payment(mock_payment_gateway, 200)
# Verify the mock's behavior
assert mock_payment_gateway.charge.call_count == 2
Met de eigenschap side_effect kan de mock verschillende waarden teruggeven of exceptions genereren bij opeenvolgende aanroepen. In dit voorbeeld geeft de eerste aanroep van charge "Success" terug, terwijl de tweede aanroep een ValueError oplevert. Dit is handig om verschillende scenario’s in één test te behandelen.
2. Functies bespioneren (spying)
Spying laat je bijhouden hoe een echte functie is aangeroepen, inclusief het aantal aanroepen en de doorgegeven argumenten. Het is vooral nuttig wanneer je wilt garanderen dat een functie wordt aangeroepen zoals verwacht, terwijl de oorspronkelijke logica toch wordt uitgevoerd:
# production code
def log_message(logger, message):
logger.info(message)
return f"Logged: {message}"
# test code
def test_log_message_with_spy(mocker):
# Spy on the info method of the logger
mock_logger = mocker.Mock()
spy_info = mocker.spy(mock_logger, "info")
# Call the function
result = log_message(mock_logger, "Test message")
# Assert the returned value
assert result == "Logged: Test message"
# Verify the spy behavior
spy_info.assert_called_once_with("Test message")
assert spy_info.call_count == 1
De methode spy() houdt bij hoe een echte methode wordt aangeroepen zonder het gedrag te wijzigen. Hier bespioneren we de info-methode van de logger om te controleren of die met het juiste bericht is aangeroepen, terwijl de methode normaal blijft uitvoeren.
Best practices voor het gebruik van pytest-mock
Mocken is krachtig, maar onzorgvuldig gebruik kan tot problemen in je tests leiden. Hier zijn enkele best practices die ik aanraad:
1. Vermijd overmatig mocken
Te veel mocks kunnen leiden tot fragiele tests die breken wanneer interne code verandert, zelfs als het externe gedrag hetzelfde blijft. Probeer alleen te mocken wat nodig is en vertrouw waar mogelijk op echte implementaties.
Overmatig mocken kan je tests sterk koppelen aan implementatiedetails van je code, waardoor zelfs kleine refactors onnodig tests laten falen. Richt je in plaats daarvan op het mocken van alleen die delen van het systeem die extern zijn of significante neveneffecten hebben.
2. Gebruik duidelijke naamgevingsconventies
Gebruik beschrijvende namen voor je mocks wanneer je objecten mockt. Dat maakt je tests leesbaarder en makkelijker te onderhouden. Gebruik bijvoorbeeld in plaats van generieke namen zoals mock_function() iets beschrijvends zoals mock_api_response().
Heldere naamgevingsconventies helpen anderen het doel van elke mock te begrijpen, verminderen verwarring en maken het eenvoudiger om de testsuite in de loop van de tijd te onderhouden.
3. Houd tests eenvoudig en gefocust
Elke test moet zich richten op één gedrag of scenario. Dat vereenvoudigt het debuggen en houdt je tests begrijpelijk. Een goed gefocuste test geeft eerder heldere feedback wanneer er iets misgaat, waardoor problemen makkelijker te identificeren en te verhelpen zijn.
Conclusie
In deze blog hebben we de basis van mocken met pytest-mock verkend en geleerd hoe je het gebruikt om onze Python-tests te verbeteren. We hebben alles behandeld van het basis-mocken van functies en methoden tot geavanceerdere technieken zoals side effects toevoegen en functies bespioneren.
Mocken is een essentiële tool voor het maken van betrouwbare en onderhoudbare tests, vooral bij complexe systemen of externe afhankelijkheden. Door pytest-mock in je projecten op te nemen, schrijf je meer geïsoleerde, voorspelbare en beter onderhoudbare tests.
Wil je je testvaardigheden verder uitbreiden, bekijk dan zeker de gratis cursus Introduction to Testing in Python op Datacamp.
FAQs
Hoe verschilt mocken van het testen van echte componenten?
Mocken vervangt echte componenten door gesimuleerde varianten, zodat je geïsoleerd en kostenefficiënt kunt testen zonder te vertrouwen op externe systemen of echte data.
Kan ik pytest-mock met andere testframeworks gebruiken?
pytest-mock is specifiek ontworpen voor pytest en integreert er naadloos mee, maar het bouwt op Python’s unittest.mock, dat je ook los kunt gebruiken.
Zijn fixtures verplicht voor mocken in pytest-mock?
Hoewel fixtures zoals mocker het mocken efficiënter maken, kun je in pytest nog steeds rechtstreeks unittest.mock gebruiken zonder fixtures, zij het met minder integratie.


Naarmate je Python-projecten complexer en groter worden, neemt ook het belang van robuuste tests toe. Testen zorgt ervoor dat je code doet wat je verwacht en dat de kwaliteit in alle delen van de applicatie behouden blijft.
Wanneer je applicatie samenwerkt met verschillende soorten databases of externe API’s zoals OpenAI GPT of Anthropic Claude, kan het testen van deze afhankelijkheden uitdagend worden. Daar komt mocken goed van pas.
Met mocken kun je objecten, functies en gedrag simuleren, waardoor je tests meer geïsoleerd en voorspelbaar worden. Het stelt je ook in staat je te richten op de specifieke functie die je wilt testen door die te isoleren van afhankelijkheden die complexiteit of onverwacht gedrag kunnen introduceren.
In deze blog leer je hoe je
pytest-mock, een krachtige plugin voor pytest, kunt gebruiken om mocks efficiënt te implementeren in je Python-tests. Tegen het einde van deze tutorial kun je mockingtechnieken toevoegen aan je testcases en ze krachtiger maken.Wat is pytest-mock?
pytest-mock is een plugin voor het populaire Python-testframework pytest die gemakkelijk toegang geeft tot mockingfunctionaliteit. Het bouwt voort op Python’s ingebouwde
unittesten vereenvoudigt het mocken tijdens tests.pytest-frameworklogo
pytest-mockverbetert de leesbaarheid en maakt het eenvoudig om mocks te implementeren in tests met een meer pytest-eigen aanpak. Of je nu losse functies, methoden van klassen of volledige objecten wilt mocken,pytest-mockbiedt de flexibiliteit die je nodig hebt voor effectief testen zonder extra complexiteit.Een belangrijke reden voor de goede leesbaarheid van
pytest-mockis de declaratieve teststijl, waarmee je verwachte aanroepen en gedrag direct in je tests kunt specificeren. Dat maakt de code makkelijker te schrijven, te lezen en te onderhouden, terwijl hij bestand blijft tegen implementatiewijzigingen die het externe gedrag niet veranderen.