Mock and patch
A Mock from unittest.mock accepts any call and records it. Pass one in where your code expects a collaborator, then ask it what happened.
from unittest.mock import Mock
mailer = Mock()
mailer.send.return_value = "queued"
print(mailer.send("ann@example.com", "Hi"))
mailer.send.assert_called_once_with(
"ann@example.com", "Hi")
print(mailer.send.call_count)return_value sets what a call gives back, and side_effect can raise an exception instead. assert_not_called() checks that nothing happened.
patch
When the code looks a collaborator up by itself, patch.object(owner, "name") swaps in a Mock for the with block and puts the original back afterwards, even if the test fails.
import shutil
from unittest.mock import patch
with patch.object(shutil,
"disk_usage") as du:
du.return_value.free = 10
print(shutil.disk_usage("/").free)
du.assert_called_once_with("/")
print(shutil.disk_usage is du)Patch the name that your code reads when it runs. Code that did from shutil import disk_usage holds its own reference, which patching shutil.disk_usage cannot reach.
Your task
remind and low_on_disk are written for you. Write test_mails_only_overdue_members (one overdue member and one not, checked with assert_called_once_with), test_no_mail_when_nothing_is_overdue (members who are not overdue, checked with assert_not_called), and test_low_on_disk, which patches shutil.disk_usage to report 10 bytes free and checks both the result and the path it was called with.
The explanation opens here once every check passes.