Advanced 30 min read

Dependency Injection in Apex

By the end of this lesson, you'll be able to:

  • Define dependency injection and explain the problem it solves
  • Refactor a class to accept its selector as a constructor parameter
  • Use dependency injection to substitute a fake selector in a unit test

Prerequisites: "Domain Layer and Trigger Handlers Revisited"

The problem: a hardcoded dependency

public with sharing class TransactionBalanceService {
    public static void applyDeltas(List<Transaction__c> transactions) {
        BankAccountSelector selector = new BankAccountSelector(); // hardcoded
        // ...uses selector to query and update balances
    }
}
  1. public with sharing class TransactionBalanceService { — the service class.
  2. public static void applyDeltas(List<Transaction__c> transactions) { — the method.
  3. BankAccountSelector selector = new BankAccountSelector(); // hardcoded — constructed internally — no way for a test to substitute anything else.
  4. // ...uses selector to query and update balances — the rest of the logic.
  5. } — closes the method.
  6. } — closes the class.

Because TransactionBalanceService constructs its own BankAccountSelector internally, a test exercising this method has no way to substitute a fake selector — every test is forced to rely on BankAccountSelector's real SOQL query actually running against test data.

Injecting the dependency instead

public with sharing class TransactionBalanceService {
    private BankAccountSelector selector;

    public TransactionBalanceService(BankAccountSelector selector) {
        this.selector = selector;
    }

    public TransactionBalanceService() {
        this(new BankAccountSelector()); // sensible real-world default
    }

    public void applyDeltas(List<Transaction__c> transactions) {
        // ...uses this.selector instead of constructing one internally
    }
}
  1. public with sharing class TransactionBalanceService { — the same class.
  2. private BankAccountSelector selector; — now a field, not a local variable.
  3. (blank line)
  4. public TransactionBalanceService(BankAccountSelector selector) { — a constructor that accepts the dependency.
  5. this.selector = selector; — stores whatever was passed in.
  6. } — closes that constructor.
  7. (blank line)
  8. public TransactionBalanceService() { — a second, no-argument constructor.
  9. this(new BankAccountSelector()); // sensible real-world default — delegates to the first constructor with a real selector.
  10. } — closes the no-argument constructor.
  11. (blank line)
  12. public void applyDeltas(List<Transaction__c> transactions) { — the method.
  13. // ...uses this.selector instead of constructing one internally — the key change.
  14. } — closes the method.
  15. } — closes the class.

The no-argument constructor still exists for normal production use (new TransactionBalanceService()), but a test can now call new TransactionBalanceService(fakeSelector) and pass in a test-only substitute — this is the entire idea of dependency injection, in nine lines of Apex.

When this actually pays off

For a class whose dependency is a simple selector, this benefit is modest — Module 42's tests already worked fine inserting real records. Dependency injection earns its keep more clearly when a dependency is expensive or hard to set up in a test at all (an external callout, Module 36, or a slow batch process, Module 38) — situations where substituting a fake becomes genuinely necessary rather than just convenient.

Exercise

As a comment, explain why TransactionBalanceService keeps a no-argument constructor that calls this(new BankAccountSelector()), rather than only having the constructor that requires a selector parameter.

Show hint

Think about what ordinary production code (like a trigger handler) would look like calling this class.

APEX

Dependency Injection in Apex Quiz

1. What problem does dependency injection solve, as shown in this lesson?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

Dependency injection means a class receives its dependencies (like a selector) from outside, rather than constructing them internally — a small change that makes it possible to substitute a fake dependency during testing.