如何使用 WebApplicationFactory 对 .NET 6 Web Api 项目进行集成测试?

How to do integration testing for .NET 6 Web Api projects using WebApplicationFactory?

我一直在使用 WebApplicationFactory 在 .NET 5、.NET Core 3.1 和 .NET Core 2.1 中进行集成测试。这对我来说一切正常。

我刚刚创建我的项目:

dotnet new webapi

然后我看到一个 Web api 项目 和一个 Startup class.

然后我创建一个工厂实例class:

private readonly WebApplicationFactory<Startup> factory = new WebApplicationFactory<Startup>();

正如解释的那样here and here

WebApplicationFactory class 是我非常喜欢的东西。它使编写集成测试变得容易。我很喜欢。 Microsoft 文档 also refer to it for .NET 6.

但是,在使用 .NET 6 时,如何确保我的代码可以编译?

提到的命令行语句:

dotnet new webapi

产生了一个运行良好的项目,但是(当安装了 .NET 6 SDK 时)没有启动 class......现在我的测试没有编译只有一个原因:启动 class不存在。

如何解决这个问题?我很难想象我不能再编写集成测试了,因为 WebApplicationFactory<Startup> 不再编译了。但现在,这就是我需要面对的现实。我该如何解决这个问题?我期待能够使用 WebApplicationFactory class 并继续编写集成测试。但是怎么办?

.NET 6 RC1 中添加了对此的支持。这是生产中支持的第一个版本。

您可以传递 Program 而不是 StartupStartup 现在基本上合并到 Program 中。 Program class 是从 Program.cs 文件中的顶级语句自动生成的。虽然它不是 public class,因此您需要将 InternalsVisibleTo 添加到应用程序的项目文件以使其对测试项目可见。

  <ItemGroup>
    <InternalsVisibleTo Include="MinimalApiPlayground.Tests" />
  </ItemGroup>

this example 中,Damien Edwards 定义了一个 WebApplicationFactory<Program> 来演示测试 Todo minimal API。

创建测试应用与使用 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);
    }
}

该示例中的 Program class 定义了很多端点:


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();