Card 08/ 09
Practice: Making a Weather Report Testable
It calls a real weather API and the system clock inside its methods.
Making a class's dependencies replaceable is the skill practised here. A WeatherReport builds a headline from today's date and a reading from a weather web service, calling both directly. Run in a container without a network, as below, it cannot produce a headline at all. Answer three questions: what are its dependencies, how would you invert them, and what would a test pass in?
from datetime import date
from urllib.request import urlopen
class WeatherReport:
def headline(self, city):
reading = urlopen(f"https://weather.example.com/now?city={city}").read()
return f"{date.today():%d %B}: {reading.decode()} in {city}"
try:
print(WeatherReport().headline("Leeds"))
except OSError as error:
print(f"{type(error).__name__}: {error}")URLError: <urlopen error [Errno -3] Temporary failure in name resolution>Show the answers
The dependencies. Two details are hidden in headline: the weather service, reached through urlopen, and the system clock, reached through date.today(). The output shows the first: with no network, the method fails before doing any work. The second makes the expected headline change every day.
The inversion. Define two small interfaces beside WeatherReport, shaped by what it needs: WeatherSource with reading_for(city) -> str, and Clock with today() -> date. Implement them with urlopen and date.today() in separate classes, and have WeatherReport take both in its constructor.
The test. Pass a fake WeatherSource that returns "light rain" and a fake Clock that returns 6 October 2026. The expected headline is then fixed, "06 October: light rain in Leeds", and the test runs with no network on any day.
← → move through sections and cards