Home > database >  ASP.NET Core 7 Integration Test with CustomWebApplicationFactory<TProgram> Client always retur
ASP.NET Core 7 Integration Test with CustomWebApplicationFactory<TProgram> Client always retur

Time:01-03

I have my asp.net core 7 web api app running using Program.cs and Startup.cs. I have my Integration Tests already written using CustomWebApplicationFactory<TStartup>. All are working as expected.

Now I have decided to move away from Startup.cs. So I have moved all Startup.cs logics inside Program.cs. My Program.cs looks like,

try
{
    //Read Configuration from appSettings
    var config = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .Build();
    //Initialize Logger
    Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(config)
                .CreateLogger();

    Log.Information($"Starting {typeof(Program).Assembly.FullName}");

    var builder = WebApplication.CreateBuilder();

    builder.Host.UseSerilog();//Uses Serilog instead of default .NET Logger
    builder.WebHost.ConfigureKestrel(options =>
    {
        // Set properties and call methods on options
        options.AddServerHeader = false;
    });

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
    //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("role", ClaimTypes.Role);

    builder.Services.AddHttpContextAccessor()
                    .AddApiClients(builder.Configuration)
                    .AddApplicationServices(builder.Configuration)
                    .AddMemoryCache()
                    .AddResponseCompression()
                    .AddApiControllersAndBehavior()
                    .AddApiVersion()
                    .AddApiAuthenticationAndAuthorization(builder.Configuration)
                    .AddSwagger(builder.Configuration)
                    .AddApplicationServices()
                    .AddCors(options =>
                    {
                        options.AddPolicy("AppClients", policyBuilder => policyBuilder.WithOrigins(builder.Configuration.GetValue<string>("WebClient"))
                                                                                        .AllowAnyHeader()
                                                                                        .AllowAnyMethod());
                    })
                    //.AddHttpLogging(options =>
                    //{
                    //    options.LoggingFields = HttpLoggingFields.All;
                    //})
                    .AddFeatureManagement()
                    .UseDisabledFeaturesHandler(new DisabledFeatureHandler());

    var consoleLogging = new ConsoleLogging(builder.Configuration.GetValue<bool>("EnableConsoleLogging"));
    builder.Services.AddSingleton(consoleLogging);

    var commandsConnectionString = new CommandConnectionString(builder.Configuration.GetConnectionString("CommandsConnectionString"));
    builder.Services.AddSingleton(commandsConnectionString);

    var queriesConnectionString = new QueryConnectionString(builder.Configuration.GetConnectionString("QueriesConnectionString"));
    builder.Services.AddSingleton(queriesConnectionString);

    if (builder.Environment.IsDevelopment())
    {
        //builder.Services.AddScoped<BaseReadContext, AppInMemoryReadContext>();
        //builder.Services.AddScoped<BaseContext, AppInMemoryContext>();
        builder.Services.AddScoped<BaseReadContext, AppSqlServerReadContext>();
        builder.Services.AddScoped<BaseContext, AppSqlServerContext>();

        builder.Services.AddMiniProfilerServices();
    }
    else
    {
        builder.Services.AddScoped<BaseReadContext, AppSqlServerReadContext>();
        builder.Services.AddScoped<BaseContext, AppSqlServerContext>();
    }

    var app = builder.Build();

    app.UseMiddleware<ExceptionHandler>()
           //.UseHttpLogging()
           .UseSecurityHeaders(SecurityHeadersDefinitions.GetHeaderPolicyCollection(builder.Environment.IsDevelopment()))
           .UseHttpsRedirection()
           .UseResponseCompression();

    if (builder.Environment.IsDevelopment())
    {
        app.UseMiniProfiler()
           .UseSwagger()
           .UseSwaggerUI(options =>
           {
               foreach (var description in app.Services.GetRequiredService<IApiVersionDescriptionProvider>().ApiVersionDescriptions)
               {
                   options.SwaggerEndpoint(
                           $"swagger/AppOpenAPISpecification{description.GroupName}/swagger.json",
                           $"App API - {description.GroupName.ToUpperInvariant()}");
               }

               options.OAuthClientId("appswaggerclient");
               options.OAuthAppName("App API");
               options.OAuthUsePkce();

               options.RoutePrefix = string.Empty;
               options.DefaultModelExpandDepth(2);
               options.DefaultModelRendering(ModelRendering.Model);
               options.DocExpansion(DocExpansion.None);
               options.DisplayRequestDuration();
               options.EnableValidator();
               options.EnableFilter();
               options.EnableDeepLinking();
               options.DisplayOperationId();
           });
    }

    app.UseRouting()
       .UseCors("AppClients")
       .UseAuthentication()
       .UseAuthorization()
       .UseRequestLocalization(options =>
       {
           var supportedCultures = new[] { "en", "en-IN", "en-US" };

           options.SetDefaultCulture("en-IN");
           options.AddSupportedCultures(supportedCultures);
           options.AddSupportedUICultures(supportedCultures);
           options.ApplyCurrentCultureToResponseHeaders = true;
       })
       .UseEndpoints(endpoints =>
       {
           endpoints.MapControllers();
       });

    await app.RunAsync();
}
catch (Exception ex)
{
    Log.Fatal(ex, "The Application failed to start.");
}
finally
{
    Log.CloseAndFlush();
}

/// <summary>
/// Added to Make FunctionalTest Compile
/// </summary>
public partial class Program { }

Here is my CustomWebApplicationFactory<TProgram>,

public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        var projectDir = Directory.GetCurrentDirectory();

        builder.ConfigureAppConfiguration((context, conf) =>
        {
            conf.AddJsonFile(Path.Combine(projectDir, "appsettings.Test.json"));
        });

        builder.UseEnvironment("Testing");

        builder.ConfigureTestServices(async services =>
        {
            services.AddAuthentication("Test")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });

            services.AddScoped(_ => AuthClaimsProvider.WithMasterClaims());

            var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(BaseContext));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(BaseReadContext));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(ITenantService));

            if (descriptor != null)
            {
                services.Remove(descriptor);
                services.AddTransient<ITenantService, TestTenantService>();
            }

            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
            var connection = new SqliteConnection(connectionStringBuilder.ToString());

            var dbContextOptions = new DbContextOptionsBuilder<AppSqliteInMemoryContext>()
                                    .UseSqlite(connection)
                                    .Options;

            services.AddScoped<BaseContext>(options => new AppSqliteInMemoryContext(dbContextOptions));

            var dbContextReadOptions = new DbContextOptionsBuilder<AppSqliteInMemoryReadContext>()
                                    .UseSqlite(connection)
                                    .Options;

            services.AddScoped<BaseReadContext>(options => new AppSqliteInMemoryReadContext(
                dbContextReadOptions, options.GetRequiredService<ITenantService>()));

            await connection.CloseAsync();

            var sp = services.BuildServiceProvider();

            using var scope = sp.CreateScope();
            var scopedServices = scope.ServiceProvider;
            var db = scopedServices.GetRequiredService<BaseContext>();
            var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<Program>>>();

            try
            {
                await db.Database.OpenConnectionAsync();
                await db.Database.EnsureCreatedAsync();
                await DatabaseHelper.InitialiseDbForTests(db);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, $"An error occurred seeding the database with test data. Error: {ex.Message}");
                throw;
            }
        });
    }

}

Here is my Integration Test,

public class GetByIdTests : IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public GetByIdTests(CustomWebApplicationFactory<Program> factory)
    {
        //factory.ClientOptions.BaseAddress = new Uri("https://localhost:44367");
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            BaseAddress = new Uri("https://localhost:44367"),
            AllowAutoRedirect = false
        });
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
        _client.DefaultRequestHeaders.Add("x-api-version", "1.0");
    }

    [Fact]
    public async Task GetById_ReturnsExpectedResponse_ForMasterUser()
    {
        var id = Guid.Parse("6B4DFE8A-2FCB-4716-94ED-4D63BF9351C6");
        using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/branches/{id}");
        var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    }
}

I have followed all the steps mentioned in 404 Error in Integration Tests

Please can you assist me on what I'm doing wrong?

CodePudding user response:

In your integration test class you configure the HttpClient with an explicit BaseAddress.

_client = factory.CreateClient(new WebApplicationFactoryClientOptions
    {
        BaseAddress = new Uri("https://localhost:44367"),
        AllowAutoRedirect = false
    });

But as far as I can see, you don't provide this configutation in your CustomWebApplicationFactory. To do this programmatically without an environment variable you can call the UseUrls() method on the IHostWebApllicationBuilder

builder.UseUrls("http://localhost:44367");

CodePudding user response:

This movement from Startup.cs to Program.cs was in my backlog for long time and every time I attempted I ended up with 404 NotFound in the tests.

Finally I figured out. Here is how.

I was comparing my Program.cs line by line with eshoponweb Program.cs and noticed that I was missing args in my CreateBuilder().

In my Program.cs, I just changed from this

var builder = WebApplication.CreateBuilder();

to

var builder = WebApplication.CreateBuilder(args); // added args here

and it started working.

The reason my Program.cs was missing args is that our sonar qube scanner was highlighting a security risk for args and so we removed that when our project was targeting ASP.NET Core 3.1 and now that was making our test to fail when the project is targeting ASP.NET Core 6 or above.

  • Related