.NET 6 WebApplicationFactory test throws System.InvalidOperationException : The entry point exited without ever building an IHost.

See original GitHub issue

Description When I have Serilog configured on my Program.cs and if I run more than one test that uses WebApplicationFactory I get an exception thrown System.InvalidOperationException : The entry point exited without ever building an IHost. accessing the application factory services.

Reproduction Program.cs

using System.Text.Json.Serialization;
using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

Log.Information("Starting up...");

try
{
    var builder = WebApplication.CreateBuilder(args);
    builder.Host.UseSerilog((ctx, lc) => lc.ReadFrom.Configuration(ctx.Configuration));

    var configuration = builder.Configuration;
    var services = builder.Services;
    // Add services to the container.

    services.AddControllers()
         .AddJsonOptions(options =>
         {
             options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
         })
         .AddControllersAsServices();
    services.AddEndpointsApiExplorer();

    services.AddSwaggerGen();

    var app = builder.Build();

    app.UseSerilogRequestLogging();

    app.UseCors(builder =>
        builder
            .WithOrigins(configuration.GetSection("AllowedOrigins")
                .AsEnumerable()
                .Select(kvp => kvp.Value)
                .Where(origin => !string.IsNullOrEmpty(origin))
                .ToArray()
            )
            .SetIsOriginAllowedToAllowWildcardSubdomains()
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseHttpsRedirection();

    app.UseAuthentication();
    app.UseAuthorization();

    app.MapControllers();

    app.Run();
    return 0;
}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly.");
    return 1;
}
finally
{
    Log.CloseAndFlush();
}

#pragma warning disable CA1050 // Declare types in namespaces
public partial class Program { } // so you can reference it in tests
#pragma warning restore CA1050 // Declare types in namespaces

appsettings.json:

{
  "AllowedHosts": "*",
  "Serilog": {
    "AllowedHosts": "*",
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "Enrich": [
      "FromLogContext"
    ],
    "Filter": [
      {
        "Name": "ByExcluding",
        "Args": {
          "expression": "@mt = 'An unhandled exception has occurred while executing the request.'"
        }
      }
    ],
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} [{Level}]: {Message:l}{NewLine}{Exception}"
        }
      }
    ]
  }
}

TestApplication.cs:

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Hosting;

namespace Api.Tests;

internal class TestApplication : WebApplicationFactory<Program>
{
    private readonly string environment;

    public TestApplication(string environment = "Development")
    {
        this.environment = environment;
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        builder.UseEnvironment(environment);
        return base.CreateHost(builder);
    }
}

SwaggerTest.cs:

using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
using Xunit;

namespace Api.Tests.Swagger;

public class SwaggerTest
{
    [Fact]
    public async Task OperationIds_Should_Be_Unique()
    {
        await using var application = new TestApplication();
        var swaggerProvider = application.Services.GetRequiredService<ISwaggerProvider>();
        var swagger = swaggerProvider.GetSwagger("v1");
        var operationIds = swagger.Paths.Values.SelectMany(path => path.Operations.Values.Select(operation => operation.OperationId)).ToList();

        operationIds.Should().OnlyHaveUniqueItems();
    }

    [Fact]
    public async Task OperationIds_Should_Be_Unique2()
    {
        await using var application = new TestApplication();
        var swaggerProvider = application.Services.GetRequiredService<ISwaggerProvider>();
        var swagger = swaggerProvider.GetSwagger("v1");
        var operationIds = swagger.Paths.Values.SelectMany(path => path.Operations.Values.Select(operation => operation.OperationId)).ToList();

        operationIds.Should().OnlyHaveUniqueItems();
    }
}

Expected behavior Tests pass, and does not throw calling application.Services

Relevant package, tooling and runtime versions

<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Expressions" Version="3.2.1" />

dotnet --version:

6.0.102

Additional context The tests pass if I comment out the line builder.Host.UseSerilog((ctx, lc) => lc.ReadFrom.Configuration(ctx.Configuration)); from Program.cs.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:9
  • Comments:28 (7 by maintainers)

github_iconTop GitHub Comments

6reactions
pikoscielniakcommented, Mar 7, 2022

Changing Serilog initialization in Program.cs from:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

to:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

solved the problem. @nblumhardt could you explain why is that?

5reactions
VictorioBerracommented, Mar 2, 2023

Same issue here.

This is an ugly issue IMO because if you try and do everything right by following the Serilog docs and use CreateBootstrapLogger() and you follow the official Microsoft Integration Testing Docs which recommend creating test classes with ITestFixture<CustomWebAppFactory<Program>> by default everything will run in parallel and you will hit this bug and then spend maybe hours fighting it before you land here on this issue from Google.

Maybe the Serilog docs could be improved to warn of this? IE:

If you use CreateBootstrapLogger() along with XUnit parallel tests, an exception may get thrown in your startup/program as the freezing of the ReloadableLogger is a stateful operation.

Or maybe there is a way for someone much smarter than me to maker a change in CreateBootstrapLogger() to circumvent this issue?

Read more comments on GitHub >

github_iconTop Results From Across the Web

The entry point exited without ever building an IHost
The error "The entry point exited without ever building an IHost" generally happens when the host failed to start because it crashed. A...
Read more >
Supporting integration tests with WebApplicationFactory in ...
NET 6 entrypoint, so failed at this point throw new InvalidOperationException();. This method uses a new type, DeferredHostBuilder , which we'll ...
Read more >
[Fix]-The entry point exited without ever building an IHost
What is the point of configuring DefaultScheme and DefaultChallengeScheme on ASP.NET Core? The command "npm run build -- --prod" exited with code 1...
Read more >
Integration Testing: IHost Lifecycle with xUnit.Net
The generic host builder introduced in .Net Core turns out to be a very effective way to bootstrap your system within automated test...
Read more >
Using custom startup class with ASP.NET Core integration ...
Custom location for web host builder confuses integration tests mechanism and we have to point out the correct location of web application ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found