简体   繁体   English

ASP.NET Core 7 与 CustomWebApplicationFactory 的集成测试<tprogram>客户端总是返回 404 not found</tprogram>

[英]ASP.NET Core 7 Integration Test with CustomWebApplicationFactory<TProgram> Client always returns 404 not found

I have my asp.net core 7 web api app running using Program.cs and Startup.cs .我的asp.net core 7 web api应用程序正在使用Program.csStartup.cs运行。 I have my Integration Tests already written using CustomWebApplicationFactory<TStartup> .我已经使用CustomWebApplicationFactory<TStartup>编写了我的Integration Tests All are working as expected.一切都按预期工作。

Now I have decided to move away from Startup.cs .现在我决定离开Startup.cs So I have moved all Startup.cs logics inside Program.cs .所以我将所有Startup.cs逻辑都移到了Program.cs中。 My Program.cs looks like,我的Program.cs看起来像,

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> ,这是我的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 ,这是我的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 official docs .我已经按照官方文档中提到的所有步骤进行操作。 However when I run the test I keep getting 404 not found.但是,当我运行测试时,我一直找不到 404。

Here is the error screen shot,这是错误的屏幕截图,

集成测试中的 404 错误

Please can you assist me on what I'm doing wrong?请你能帮我解决我做错了什么吗?

In your integration test class you configure the HttpClient with an explicit BaseAddress.在您的集成测试 class 中,您使用显式 BaseAddress 配置 HttpClient。

_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.但据我所知,您没有在 CustomWebApplicationFactory 中提供此配置。 To do this programmatically without an environment variable you can call the UseUrls() method on the IHostWebApllicationBuilder要在没有环境变量的情况下以编程方式执行此操作,您可以在 IHostWebApllicationBuilder 上调用 UseUrls() 方法

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

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.Startup.csProgram.cs的这种移动在我的积压工作中存在了很长时间,每次我尝试时,我都会在测试中以404 NotFound告终。

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() .我逐行比较我的Program.cseshoponweb Program.cs并注意到我在我的CreateBuilder()中缺少args

In my Program.cs , I just changed from this在我的Program.cs中,我只是从这个改变

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.我的Program.cs缺少args的原因是我们的sonar qube扫描仪突出了argssecurity risk ,因此当我们的项目以ASP.NET Core 3.1为目标时,我们删除了它,现在当项目以ASP.NET Core 6为目标时,这使我们的测试失败ASP.NET Core 6或以上。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM