I have been using WebApplicationFactory for integration testing in .NET 5, .NET Core 3.1 and .NET Core 2.1 . It is all working fine for me.
I just create my project with:
dotnet new webapi
and then I see a web api project with a Startup class.
Then I create an instance of the factory class:
private readonly WebApplicationFactory<Startup> factory = new WebApplicationFactory<Startup>();
The WebApplicationFactory class is something I really like. It makes it easy to write integration tests. I like it a lot. The Microsoft docs also refer to it for .NET 6.
However, how do I, when using .NET 6, make sure my code compiles?
The mentioned command-line statement:
dotnet new webapi
results in a nicely working project but (when having the .NET 6 SDK installed) without a Startup class...... Now my tests do not compile for just one reason: the Startup class does not exist.
How to fix this? I find it hard to imagine that I cannot write integration tests anymore because WebApplicationFactory<Startup>
does not compile anymore. But right now, this is the reality I need to deal with. How can I solve the problem? I look forward to being able to use the WebApplicationFactory
class and continue writing integration tests. But how?
Support for this was added in .NET 6 RC1. This is the first version to be supported in production.
You can pass Program
instead of Startup
. Startup
essentially merged into Program
now. The Program
class is automagically generated from the top-level statements in the Program.cs
file. It's not a public class though, so you need to add InternalsVisibleTo
to the application's project file to make it visible to the test project.
<ItemGroup>
<InternalsVisibleTo Include="MinimalApiPlayground.Tests" />
</ItemGroup>
In this example Damien Edwards defines a WebApplicationFactory<Program>
to demonstrate testing a Todo minimal API.
Creating a test app is essentially the same as using Startup
:
:
[Fact]
public async Task GetSwaggerUI_Returns_OK()
{
await using var application = new TodoApplication();
var client = application.CreateClient();
var response = await client.GetAsync("/swagger/index.html");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
class TodoApplication : WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
// Add mock/test services to the builder here
builder.ConfigureServices(services =>
{
services.AddScoped(sp =>
{
// Replace SQL Lite with test DB
return new SqliteConnection("Data Source=testtodos.db");
});
});
return base.CreateHost(builder);
}
}
The Program class in that example defines quite a lot of endpoints :
using System.ComponentModel.DataAnnotations;
using Microsoft.Data.Sqlite;
using Dapper;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("TodoDb") ?? "Data Source=todos.db";
builder.Services.AddScoped(_ => new SqliteConnection(connectionString));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
await EnsureDb(app.Services, app.Logger);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
app.MapGet("/error", () => Results.Problem("An error occurred.", statusCode: 500))
.ExcludeFromDescription();
app.MapSwagger();
app.UseSwaggerUI();
app.MapGet("/", () => "Hello World!")
.WithName("Hello");
...
app.MapDelete("/todos/delete-all", async (SqliteConnection db) => Results.Ok(await db.ExecuteAsync("DELETE FROM Todos")))
.WithName("DeleteAll")
.Produces<int>(StatusCodes.Status200OK);
app.Run();