Test DI service registration

September 22nd 2023 Dependency Injection Unit Testing .NET

In large projects, service registration code for dependency injection can grow rather large. Forgetting to register a newly added dependency will break your application at the point of instantiating a class (transitively) requiring it. It's even more likely that this will happen to you if you need to register a dependency in multiple places (e.g., for multiple projects) and don't have tests for every one of them.

To avoid having to register services in multiple places even if they are used in multiple projects, you can create an extension method for registering a full group of interconnected services:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDependencies(this IServiceCollection services)
    {
        services.AddScoped<IDependency, Dependency>();
        services.AddScoped<IDependency1, Dependency1>();
        services.AddScoped<IDependency2, Dependency2>();
        services.AddScoped<IDependency3, Dependency3>();
        return services;
    }
}

Then, instead of registering the individual services for each project, you can simply call the extension method:

services.AddDependencies();

Even more importantly, when adding a new dependency, you only need to register it in a single place. If you add it to the extension method, the change will automatically apply to all projects that are using it without any other code changes:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDependencies(this IServiceCollection services)
    {
        services.AddScoped<IDependency, Dependency>();
        services.AddScoped<IDependency1, Dependency1>();
        services.AddScoped<IDependency2, Dependency2>();
        services.AddScoped<IDependency3, Dependency3>();
        services.AddScoped<IDependency4, Dependency4>();
        return services;
    }
}

Having such an extension method for registering a group of dependencies also makes it easier to have tests for dependency injection in place:

[Test]
public void DependencyInstantiates()
{
    var services = new ServiceCollection();
    services.AddDependencies();
    var serviceProvider = services.BuildServiceProvider();

    var dependency = serviceProvider.GetRequiredService<IDependency>();

    Assert.NotNull(dependency);
}

You can make such a test for every dependency with its own entry point (e.g., controllers in an ASP.NET Core Web API project). If you then forget to register a new dependency required by one of those, the test will fail with a very descriptive exception:

System.InvalidOperationException : Unable to resolve service for type 'DiServiceRegistration.IDependency4' while attempting to activate 'DiServiceRegistration.Dependency3'.

To see the approach in a sample project, you can check my GitHub repository. If you look at the individual commits, you will see how the test fails when a new dependency is added without being registered and how registering it makes the test pass again.

Unit tests are a great safety net for your code. They will catch bugs early in the development phase before the project is deployed to production or even made available to testers. When using extension methods for registering services, you can take advantage of them for testing your dependency injection setup as well.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License