2

My Program.cs file looks like this:

public class Program
{
    private static IConfigurationRoot _apiConfiguration = null!;

    public static async Task Main(string[] args)
    {
        try
        {
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

            if (environment == Environments.Development)
            {
                _apiConfiguration = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables()
                    .Build();
            }
            else
            {
                _apiConfiguration = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .AddEnvironmentVariables()
                    .Build();
            }

            await CreateHostBuilder(args).Build().RunAsync();
        }
        catch (Exception ex)
        {
            // log
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(loggingBuilder =>
            {
                Log.Logger = new LoggerConfiguration()
                    .ReadFrom.Configuration(_apiConfiguration) // This throws System.NullReferenceException: 'Object reference not set to an instance of an object.' as _apiConfiguration is null
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("ApplicationName", "WeatherAPI")
                    .WriteToOtlpSerilog()
                    .CreateLogger();
                loggingBuilder.AddSerilog();
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

I'm trying to write a integration test for the controller using WebApplicationFactory. However, with my simple setup, I have encountered a NullReference exception for _apiConfiguration inside the ConfigureLogging setup method.

When I run the integration test, the Main method is not called and the ConfigureLogging method is called directly which fails as the _apiConfiguration is not set. This is set when the Main method is called.

My integration test for the controller looks like this:

[TestClass]
public class WeatherControllerTests : WebApplicationFactory<Program>
{
    [TestMethod]
    public async Task TestAsync()
    {
        var client = CreateClient();

        var response = await client.GetAsync("/weather/getforecast");

        Assert.IsNotNull(response);
    }
}

I have tried to override the ConfigureWebHost and configure the logging but this doesn't help as the main logging setup is called regardless.

How can I fix this?

1 Answer 1

1

The mistake is using a static variable, especially in Main.
This creates a dependency on main, effectively making your integration untestable.

Many tutorials still start with "Log.Logger = ..." and that's wrong: because you don't have access to context.Configuration yet, in your test.

Here is an example of how to manage the Program.cs without being dependent on the execution of the Main.
The configurations are done directly in the method that can also be called by the test and is always executed.

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        try
        {
            Log.Information("Starting web host");
            await host.RunAsync();
        }
        catch (Exception ex)
        {
            // log
        }
        finally
        {
           // log close and flush
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((context, config) =>
            {
                var env = context.HostingEnvironment;

                config
                .AddJsonFile("appsettings.json", 
                    optional: false, 
                    reloadOnChange: true)
                 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", 
                    optional: true)
                 .AddEnvironmentVariables();
            })

//This is Serilog Deferred Initialization: it means that Serilog configures 
//itself after context.Configuration has been loaded by ConfigureAppConfiguration

            .UseSerilog((context, services, configuration) =>
            {
                configuration
                    .ReadFrom.Configuration(context.Configuration)
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("ApplicationName", "WeatherAPI")
                    .WriteTo.Console(); 
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Now:

  1. CreateHostBuilder() is visible and used by the framework

  2. Configuration is loaded correctly

  3. Serilog is initialized after context.Configuration is available

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.