Card 05/ 09
Dependency Inversion Example: Testing With a Fake Repository
The same service, a fake in memory, and a test that needs no database.
Fast, reliable tests are the most common reason teams invert dependencies, and this example shows that payoff directly. It tests the inverted ReminderService with a fake UserSource that keeps its data in a dictionary, so the test needs no database, no network and no setup.
A fake is a working implementation that takes a shortcut unsuitable for production, here keeping users in memory. Because ReminderService only knows UserSource, it cannot tell the fake from the MySQL implementation, and does not need to.
from dip_inverted import ReminderService
class FakeUserSource:
def __init__(self, emails):
self.emails = emails
def email_for(self, user_id):
return self.emails[user_id]
def test_remind_uses_the_users_email():
service = ReminderService(FakeUserSource({7: "[email protected]"}))
assert service.remind(7) == "MAIL to [email protected]: reset your password"
def test_remind_reaches_a_different_user():
service = ReminderService(FakeUserSource({8: "[email protected]"}))
assert service.remind(8) == "MAIL to [email protected]: reset your password"
for test in (test_remind_uses_the_users_email, test_remind_reaches_a_different_user):
test()
print(f"{test.__name__} passed")test_remind_uses_the_users_email passed
test_remind_reaches_a_different_user passedBoth tests pass on any machine, with nothing else running. Each one states its own data in the line that uses it, so a reader sees why the expected email is right without opening a database. The plain functions here would run unchanged under pytest, which collects functions named test_ in the same way.
Compare this with testing a ReminderService that opens its own MySQL connection, which fails before reaching the logic. The reminder rule did not change at all between the two versions. Only the direction of one dependency did, and that alone moved the test from "needs a MySQL server" to "runs anywhere".
The same move works for any detail: a fake clock that returns a fixed date, a fake mailer that records what it was asked to send, a fake payment gateway that approves or declines on command. In each case the policy owns a small interface, and the test supplies a version of it that it controls.
← → move through sections and cards