Should I test that?

Should I test dependency injection configuration?

Verdict

Yes

Write one CI test that builds the production dependency injection container and creates every registered service, and do not write a test for each registration.

Why

Test dependency injection configuration minimally: one CI test that builds the production container. For a web API whose container creates objects on first use, as ASP.NET Core does, Blast radius is users, because a missing registration breaks the endpoint that needs it. Change frequency is regularly, since each new service or constructor parameter edits the registrations. Detectability is same-day: the app starts and the health check passes, and the first request to the broken endpoint logs an HTTP 500. Reversibility is trivial, because a failed resolution writes no data, and Test cost is moderate, since the test needs placeholder secrets. Rule R12 gives Test minimally.

When the decision changes
WhenDecisionWhy
The container creates every service at startup, as Spring Boot does by default, and the deploy waits for a health checkDo not test the registrations; keep the startup failure and the health checkDetectability rises to immediately, because a missing bean stops the application before it serves a request
The composition root calls constructors by hand, without a container, and each parameter has its own typeDo not test a hand-written composition rootDetectability rises to immediately, because the compiler rejects a missing or wrong argument
Registrations differ by environment, such as a fake email sender outside productionTest: build the container per environment and assert the type of each service that differsDetectability moves to eventually and Reversibility to with-effort, because a fake sender in production drops emails silently
The container picks a sandbox or a live payment client from an environment settingTest mandatory: assert that the production container resolves the live payment clientBlast radius rises to money, Detectability moves to eventually and Reversibility to costly, because sandbox orders are never charged
A service keeps the current user or tenant in a fieldTest mandatory: resolve the service in two scopes and assert two different instancesBlast radius rises to safety-or-legal, Detectability to never and Reversibility to impossible, because a singleton shows one user's data to everyone
The application is an internal admin toolDo not test the registrations of an internal toolBlast radius falls to internal, because no customer reaches the failing page

What breaks if you don't test

A developer adds an IExchangeRateClient parameter to the class behind one report endpoint and forgets to register it. The code compiles, and the unit tests pass, because they hand a mock to the constructor. Production starts, because nothing resolves the report class at startup. Every customer who opens the report gets an error page until someone reads the log.

What you lose if you over-test

The common over-test asserts each registration: IInvoiceExporter maps to InvoiceExporter as scoped. It repeats the registration file, so every new service takes two edits and a renamed class fails a test while the application works. It still passes when InvoiceExporter needs a service that nobody registered, because it reads the list instead of building anything.

How to test

Build the container in one CI test as production does: production environment name, real registration code, placeholder secrets. In .NET, turn on ValidateOnBuild and scope validation; the build then lists every service it cannot create and every scoped service inside a singleton. ASP.NET Core does not register controllers in the container by default, so call AddControllersAsServices or the check skips them. In Spring Boot with lazy initialization on, run the context test with spring.main.lazy-initialization=false, because the Spring Boot reference warns that a misconfigured lazy bean fails only on first use.

When the answer changes

  • Registrations branch on the environment or a feature flag, so production runs a graph that no developer runs.
  • A service keeps the current user or tenant in a field, so a wrong lifetime leaks data between customers.
  • The container creates every service at startup and the deploy waits for a health check.

Real incident + Code example

The export that worked only in development

On an ASP.NET Core API I worked on, a refactor gave the CSV export service a currency converter dependency. The developer registered it inside the IsDevelopment() block, next to a fake exchange rate client. Local runs passed, and so did the container validation that ASP.NET Core turns on only in Development. In production every export returned HTTP 500 until a customer wrote to support the next morning. The test we added builds the production container:

[Fact]
public void Production_container_can_create_every_service()
{
    var builder = WebApplication.CreateBuilder(new WebApplicationOptions
    {
        EnvironmentName = Environments.Production,
        ApplicationName = typeof(ExportController).Assembly.GetName().Name,
    });
    builder.Configuration["ConnectionStrings:Main"] = "Host=placeholder";
    builder.Services.AddApiServices(builder.Configuration, builder.Environment);
    builder.Services.AddControllers().AddControllersAsServices();
    builder.Host.UseDefaultServiceProvider(options =>
    {
        options.ValidateOnBuild = true; // every registered service can be created
        options.ValidateScopes = true;  // no scoped service inside a singleton
    });

    using var app = builder.Build(); // throws, listing each service that fails
}

The test fails on a missing registration, a scoped service inside a singleton, and a registration made only in Development.

FAQ

Should I be unit testing my bootstrapper?

Yes, test a bootstrapper with one test that builds its production container and creates every registered service. Skip line-by-line tests of a bootstrapper, because they repeat its registrations.

Should I write a unit test for each service registration?

No, a unit test for each service registration repeats the registration code and misses a dependency that nobody registered. One test that builds the whole container catches missing dependencies in every registration at once.

How do I test that my DI container can resolve every service?

Build the container with production settings and let it create every registered service; in .NET, ValidateOnBuild on ServiceProviderOptions does this in one call. Register ASP.NET Core controllers as services first, because the container validates only the classes it holds.

Should I test service lifetimes in dependency injection?

Test the lifetime of every service that holds the current user or tenant: resolve it in two scopes and assert two different instances. For other services, ValidateScopes in the container test catches a scoped service inside a singleton.