Integration testing of .NET 7 ASP.NET apps with Minimal Hosting Model ⚡️🧪

ExecuteAutomation
ExecuteAutomation
Published in
3 min readJan 26, 2023

For starters, if you create any ASP.NET WebAPI project or WebApp project, you will notice, there wont be any Startup.csfile 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 and Program.cs into a single Program.cs file.
  • Uses top-level statements to minimize the code required for an app.
  • Uses global using directives to eliminate or minimize the number of using 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 method
  • await 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

https://www.udemy.com/course/testing-eda-microservices/

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

--

--

ExecuteAutomation
ExecuteAutomation

ExecuteAutomation Ltd is a Software testing and its related information service company founded in 2020. Info available in YouTube and Udemy as video courses .