Course contentsShow
← C#
Lesson 1710 of 1,90815. Software Quality and TestingFree lesson

Applying the Service Locator Anti-pattern Fix

Migrate legacy code from service locator patterns to proper constructor injection.

In this lesson, you will learn how to transition from hidden, runtime-dependent Service Locators to explicit, compile-time Constructor Injection to improve the testability and maintainability of your legacy codebases.

The Hidden Dependency Trap

The Service Locator pattern—often manifesting as a static ServiceLocator.Current.GetInstance<T>() or a shared IServiceProvider—is a major hurdle for unit testing. It masks dependencies, making it impossible to see what a class requires just by looking at its constructor. When testing, you are forced to configure a global state before the test runs, which leads to fragile, order-dependent test suites.

Migrating to Constructor Injection

To fix this, we shift the responsibility of dependency resolution from the object itself to the composition root (the DI container). Instead of "pulling" services, the class should "receive" them.

Refactoring Example

Legacy Code:

public class OrderProcessor {
    public void Process(Order order) {
        // Hidden dependency creates tight coupling
        var repository = ServiceLocator.Current.GetInstance<IOrderRepository>();
        repository.Save(order);
    }
}

Refactored Code:

public class OrderProcessor {
    private readonly IOrderRepository _repository;

    // Dependencies are now explicit and easily mockable
    public OrderProcessor(IOrderRepository repository) {
        _repository = repository;
    }

    public void Process(Order order) => _repository.Save(order);
}

Impact on Testing

By moving to constructor injection, you no longer need to mock the entire service locator or global container. In your unit tests, you can simply pass an IMock<IOrderRepository> directly into the constructor. This eliminates the need for complex setup logic and allows your tests to run in isolation without side effects.

While it may seem tedious to update constructors across a legacy system, the immediate benefit is a dramatic increase in compile-time safety and a cleaner, self-documenting API. When your classes declare their needs upfront, your tests become simpler to write and significantly more reliable.

Key Takeaway: If a class must ask the container for its dependencies, it is hiding its requirements; move those dependencies to the constructor to make your code testable by design.