patternpythonMajorpending
Pattern: Dependency injection for testable code
Viewed 0 times
dependency-injectionDItestingmockprotocolinterface
Problem
Code that creates its own dependencies (database connections, API clients, file system access) is hard to test because you can't substitute fakes.
Solution
Pass dependencies in instead of creating them inside:
# BAD - hard to test:
class OrderService:
def __init__(self):
self.db = PostgresDatabase() # Creates own dependency
self.email = SMTPClient() # Can't substitute in tests
def place_order(self, order):
self.db.save(order)
self.email.send(order.user, 'Order placed')
# GOOD - dependencies injected:
class OrderService:
def __init__(self, db, email):
self.db = db
self.email = email
def place_order(self, order):
self.db.save(order)
self.email.send(order.user, 'Order placed')
# Production:
service = OrderService(
db=PostgresDatabase(url=os.environ['DB_URL']),
email=SMTPClient(host='smtp.example.com'),
)
# Tests:
service = OrderService(
db=InMemoryDatabase(),
email=FakeEmailSender(),
)
service.place_order(test_order)
assert fake_email.sent[-1].to == 'user@test.com'
# Python: Use protocols/ABCs for interface contracts
from typing import Protocol
class Database(Protocol):
def save(self, entity) -> None: ...
def find(self, id) -> Any: ...
# BAD - hard to test:
class OrderService:
def __init__(self):
self.db = PostgresDatabase() # Creates own dependency
self.email = SMTPClient() # Can't substitute in tests
def place_order(self, order):
self.db.save(order)
self.email.send(order.user, 'Order placed')
# GOOD - dependencies injected:
class OrderService:
def __init__(self, db, email):
self.db = db
self.email = email
def place_order(self, order):
self.db.save(order)
self.email.send(order.user, 'Order placed')
# Production:
service = OrderService(
db=PostgresDatabase(url=os.environ['DB_URL']),
email=SMTPClient(host='smtp.example.com'),
)
# Tests:
service = OrderService(
db=InMemoryDatabase(),
email=FakeEmailSender(),
)
service.place_order(test_order)
assert fake_email.sent[-1].to == 'user@test.com'
# Python: Use protocols/ABCs for interface contracts
from typing import Protocol
class Database(Protocol):
def save(self, entity) -> None: ...
def find(self, id) -> Any: ...
Why
DI decouples components from their dependencies, making code testable with fakes and flexible to swap implementations.
Revisions (0)
No revisions yet.