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
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.