A unit test builds the class it checks by itself: new SlotFinder(calendar). It never asks the container how the app assembles its parts, and it reads no configuration. But that is exactly where errors arise that show up only in production: a service with the wrong lifetime, a setting that is named differently in the Bicep template than in the code, a factory that under certain circumstances returns null. In K2 you wrote a first startup test for this. In this lesson you turn it into a test that starts the Kladde the way it starts in Azure and resolves every registration of its own.

What the container checks at startup

The .NET container can run two checks when it is built:

  • ValidateOnBuild goes through every registration when building and checks whether each service's constructor can be satisfied with the registered services. If a dependency is missing, startup fails instead of the first request.
  • ValidateScopes forbids fetching a service with the Scoped lifetime from the root container or putting it into a singleton. A singleton that holds on to a scoped service otherwise keeps it forever, for example a DbContext with all the objects it ever loaded.

WebApplication.CreateBuilder and Host.CreateApplicationBuilder turn on both only in the Development environment. The following program registers a singleton that needs a scoped service and builds the host once as Development and once as Production:

C#
// captured.cs
#:package Microsoft.Extensions.Hosting@10.0.14

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
{
    Args = args,
    EnvironmentName = args.FirstOrDefault() ?? Environments.Production,
});
builder.Services.AddScoped<OfficeCalendar>();
builder.Services.AddSingleton<SlotFinder>();

try
{
    using var host = builder.Build();
    Console.WriteLine($"{builder.Environment.EnvironmentName}: built, the error goes unnoticed.");
}
catch (AggregateException ex)
{
    Console.WriteLine($"{builder.Environment.EnvironmentName}: {ex.InnerExceptions[0].Message}");
}

public sealed class OfficeCalendar;

public sealed class SlotFinder(OfficeCalendar calendar)
{
    public OfficeCalendar Calendar { get; } = calendar;
}
Bash
dotnet run captured.cs -- Development
dotnet run captured.cs -- Production
Development: Error while validating the service descriptor 'ServiceType: SlotFinder Lifetime: Singleton ImplementationType: SlotFinder': Cannot consume scoped service 'OfficeCalendar' from singleton 'SlotFinder'.
Production: built, the error goes unnoticed.

Because the Kladde runs as Production in production and your integration tests should also manage without Development, you turn on both checks explicitly in the test. Outside Development they are off for the sake of speed: ValidateOnBuild costs time at startup, ValidateScopes at resolutions.