Integration testing of .NET 7 ASP.NET apps with Minimal Hosting Model ⚡️🧪
For starters, if you create any ASP.NET WebAPI project or WebApp project, you will notice, there wont be any Startup.cs
file anymore, rather it just has an Program.cs
file with all bindings. This is because of new minimal hosting model introduced in .NET 6. I have discussed about even app development about it in our medium post before.
The minimal hosting model:
- Significantly reduces the number of files and lines of code required to create an app. Only one file is needed with four lines of code.
- Unifies
Startup.cs
andProgram.cs
into a singleProgram.cs
file. - Uses top-level statements to minimize the code required for an app.
- Uses global
using
directives to eliminate or minimize the number ofusing
statement lines required.
Prior to .NET 7 Integration testing of Apps with WebApplicationFactory<>
Before .NET 7 or 6, we use to invoke an application using Startup.cs
file like this
[Collection("IntegrationTests")]
public class TestCustomerSubscriberViaDB : IClassFixture<WebApplicationFactory<Startup>>
{
public TestCustomerSubscriberViaDB(
WebApplicationFactory<Program> testFixtureBase,
ITestOutputHelper testOutputHelper)
{
_testFixtureBase = testFixtureBase;
_testOutputHelper = testOutputHelper;
// Invoke the app
_testFixtureBase.CreateDefaultClient();
}
//Logic sits here after the application is invoked
}
As you can see the above class, the Startup
class being called in the WebApplicationFactory
class
.NET 7 Minimal hosting model
Since we don’t haveStartup.cs
file anymore, the only way we can invoke the application is by using Program
class
But, this is not as straightfoward as you think, since you will not have the acess to Program
class as by default, since they wont have any publicly accessible class name or namespace if there more than one Program
class in same solution as shown below
using EDA_Customer.Data;
using EDA_Customer.RabbitMq;
using Microsoft.EntityFrameworkCore;
using Shared.RabbitMQ;
using Shared.Settings;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<IRabbitMqUtil, RabbitMqUtil>()
.UseRabbitMqSettings()
.AddScoped<IRabbitScopedService, RabbitScopeService>()
.AddScoped<IDataRepository, DataRepository>()
.AddHostedService<RabbitMqService>();
builder.Services.AddControllers();
// Add services to the container.
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseSqlite(@"Data Source=customer.db"));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<CustomerDbContext>();
await dbContext.Database.EnsureCreatedAsync();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
So, how do we invoke the Program
in WebApplicationFactory
<> then ?
Well, we need to explicity add the namespace and class name for our Program.cs
as shown below
using EDA_Customer.Data;
using EDA_Customer.RabbitMq;
using Microsoft.EntityFrameworkCore;
using Shared.RabbitMQ;
using Shared.Settings;
namespace EDA_Customer;
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddSingleton<IRabbitMqUtil, RabbitMqUtil>()
.UseRabbitMqSettings()
.AddScoped<IRabbitScopedService, RabbitScopeService>()
.AddScoped<IDataRepository, DataRepository>()
.AddHostedService<RabbitMqService>();
builder.Services.AddControllers();
// Add services to the container.
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseSqlite(@"Data Source=customer.db"));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<CustomerDbContext>();
await dbContext.Database.EnsureCreatedAsync();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
}
}
As you can see in the above code, we have added
- namespace
EDA_Customer
- class
Program
main
methodawait app.RunAsync();
With these above changes, we can now invoke application in our integration test code with WebApplicationFactory<Program>
as shown below
[Collection("IntegrationTests")]
public class TestCustomerSubscriberViaDB : IClassFixture<WebApplicationFactory<Program>>
{
public TestCustomerSubscriberViaDB(
WebApplicationFactory<Program> testFixtureBase,
ITestOutputHelper testOutputHelper)
{
_testFixtureBase = testFixtureBase;
_testOutputHelper = testOutputHelper;
// Invoke the app
_testFixtureBase.CreateDefaultClient();
}
//Logic sits here after the application is invoked
}
Thats all folks !
Complete Course and more details
This whole article is part of my course in Udemy, where I have covered even more details related to all the testing approaches that we have discussed about in this article
Course in Udemy
If you think this course is something for you and wanted to enrol, do send out an email to karthik@techgeek.co.in or comment below, will send you the latest coupon code of discount.
Here is the launch coupon code which will valid till 29th of next year Jan 2022 EA_LAUNCH_23