When the same method is called multiple times in your code, you often need each call to behave differently — for example, fail first and then succeed on retry. Instead of writing multiple mocks or custom logic, you can use MagicMock.side_effect to define what happens on each consecutive call.
# test_weather.py
# test_weather.py
from unittest.mock import MagicMock
from weather import get_temperature_with_retry
def test_retries_after_transient_failure():
client = MagicMock()
# First two calls raise TimeoutError, third call succeeds
client.get_current.side_effect = [
TimeoutError(),
TimeoutError(),
{"temp_c": 21},
]
temp = get_temperature_with_retry(client, "Ljubljana", retries=3)
assert temp == 21
assert client.get_current.call_count == 3
# weather.py
# weather.py
from typing import Protocol
class WeatherClient(Protocol):
def get_current(self, city: str) -> dict:
"""Return {'temp_c': int} or raise TimeoutError on failure."""
def get_temperature_with_retry(client: WeatherClient, city: str, retries: int = 3) -> int:
attempts = 0
while attempts < retries:
try:
data = client.get_current(city)
return data["temp_c"]
except TimeoutError:
attempts += 1
raise RuntimeError("weather service unavailable after retries")