Dependency Injection

Don't build your tools inside the house. Have them handed to you.

The idea

Dependency Injection (DI) is a design pattern where an object receives its dependencies from the outside, rather than creating them itself. This achieves Inversion of Control, making your code significantly more modular, reusable, and most importantly: testable.

Step 1: Hardcoded Dependency. The class creates its own database connection.

How it works (Constructor Injection)

Instead of hardcoding a dependency like a Database inside a class, you require it to be passed into the constructor (or initialized via a framework like Spring or Angular).

# VULNERABLE TO COUPLING (Hardcoded)
class UserService:
    def __init__(self):
        # Tied forever to Postgres! Hard to test without a real DB.
        self.db = PostgresDB()

    def get_user(self, id):
        return self.db.query(id)

# SECURE (Dependency Injected)
class UserService:
    def __init__(self, db: DatabaseInterface):
        # It doesn't care what DB it is, as long as it fits the interface.
        self.db = db

Watch out for

Worked example

You write a `PaymentProcessor` that hardcodes `StripeAPI()`. A year later, the company switches to PayPal. You have to rewrite the `PaymentProcessor`. Worse, when writing unit tests, you accidentally charge a real credit card because the real Stripe API was hardcoded! If you had used DI, you could have easily passed a `MockPaymentAPI()` during tests.

Check yourself

What is the primary benefit of Dependency Injection when writing Unit Tests?

Not quite — DI has negligible impact on runtime performance.
Exactly! You can completely isolate the class you are testing by injecting predictable mocks.