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.

Easy Python 3.9+ Aug 9, 2026 Testing & modern typing 16 views 0 copies

Python code

34 lines
Python 3.9+
import 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

stdout
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

  1. Use a Protocol from typing to define the Config interface more explicitly
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Testing & modern typing

Related tutorials and quizzes for this topic.