Интеграционный тест ASP.NET Core 7 с клиентом CustomWebApplicationFactory<TPgram> всегда возвращает 404, не найдено

У меня есть мойasp.net core 7 web apiприложение работает с использованием и . У меня есть мойIntegration Testsуже написано с использованиемCustomWebApplicationFactory<TStartup>. Все работает как положено.

Теперь я решил отойти от . Итак, я переместил всеStartup.csлогика внутри. Мой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 { }

Вот мой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;
            }
        });
    }

}

Вот мой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);
    }
}

Я выполнил все шаги, указанные в официальных документах . Однако, когда я запускаю тест, я продолжаю получать 404 не найдено.

Вот скриншот ошибки,

Пожалуйста, не могли бы вы помочь мне, что я делаю неправильно?

2 ответа

Это движение отStartup.csto был в моем отставании в течение долгого времени, и каждый раз, когда я пытался, я заканчивал404 NotFoundв тестах.

Наконец я понял. Вот как.

Я сравнивал свою строку за строкой с eshoponweb и заметил, что мне не хватает в моемCreateBuilder().

В моем я только что изменился с этого

var builder = WebApplication.CreateBuilder();

к

var builder = WebApplication.CreateBuilder(args);// здесь добавлены аргументы

и он начал работать.

Причина мояProgram.csотсутствовало то, что нашsonar qubeсканер выделялsecurity riskдляargsи поэтому мы удалили это, когда наш проект был нацелен наASP.NET Core 3.1и теперь это привело к тому, что наш тест провалился, когда проект нацеленASP.NET Core 6или выше.

В своем классе интеграционного теста вы настраиваете HttpClient с явным BaseAddress.

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

Но, насколько я вижу, вы не предоставляете эту конфигурацию в своей CustomWebApplicationFactory. Чтобы сделать это программно без переменной среды, вы можете вызвать метод UseUrls() в IHostWebApplicationBuilder.

      builder.UseUrls("http://localhost:44367");
Другие вопросы по тегам