Dependency Injection in Python for Testability
Inject a config dependency into a service so you can swap a real environment-based config for a fake one in tests.
Python code
34 linesimport os
class Config:
"""Simple config loader that can be easily faked in tests."""
def get(self, key, default=None):
return os.environ.get(key, default)
class UserService:
def __init__(self, config):
self.config = config
def get_timeout(self):
return int(self.config.get("TIMEOUT", "30"))
class FakeConfig:
def __init__(self, values):
self.values = values
def get(self, key, default=None):
return self.values.get(key, default)
if __name__ == "__main__":
real_config = Config()
fake_config = FakeConfig({"TIMEOUT": "5"})
real_service = UserService(real_config)
fake_service = UserService(fake_config)
print(f"Real timeout: {real_service.get_timeout()}")
print(f"Fake timeout: {fake_service.get_timeout()}")
Output
Real timeout: 30
Fake timeout: 5
How it works
The UserService constructor takes a config object as a dependency, so it never touches os.environ directly. This lets a test pass a FakeConfig that returns predetermined values without touching the real environment. Config reads from os.environ, while FakeConfig uses an in-memory dictionary. Both expose the same get interface, so the service works with either. This is the classic dependency injection pattern: depend on an abstraction (the interface), not the concrete implementation.
Common mistakes
- Instantiating dependencies inside the class instead of accepting them in the constructor
- Not defining an interface or relying on duck typing, leading to broken fakes that miss methods
- Using global environment variables directly in the service, making tests non-deterministic
Variations
- Use a Protocol from typing to define the Config interface more explicitly
- Use a library like `dependency-injector` for complex DI graphs
Real-world use cases
- Injecting a mock database client to test service logic without a real database connection.
- Passing a fake email sender to test that notifications are triggered without sending real emails.
- Swapping a real payment gateway for a stub in integration tests to avoid actual charges.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
- Format Data with Type Hints in Python easy
Keep learning
Related tutorials and quizzes for this topic.