如何在www.example.com上下文中为Marten(在PostgreSQL之上)编写包含异步投影的集成测试asp.net?

pbwdgjma  于 2022-11-29  发布在  PostgreSQL
关注(0)|答案(1)|浏览(130)

我正在寻找一种方法来测试使用Marten编写的功能。不仅仅是作为单元测试,而是测试与asp.net API的集成和实际保存到数据库的情况。此外,我还想测试是否生成了异步投影。因此,总结一下,我想测试以下代码是否正确执行:
1.域驱动设计样式的命令由控制器操作接收
1.该命令被转换为域事件,并且该事件已保存到PostgreSQL
1.稍后,将异步生成投影并将其保存到PostgreSQL
Marten没有在他们的文档中提供具体的示例,他们确实有unit-test in their codebase,但在我看来,这些还不够独立,不足以提供足够的信息来构建自己的集成测试。
给定以下设置(受Marten示例启发):

命令

public record StartQuest(
    Guid Id, 
    string Name, 
    int Day, 
    string Location, 
    params string[] Members) : IRequest;

汇总、预测和事件

public class Quest
{
    public Guid Id { get; set; }
}

public class QuestPartyProjection : SingleStreamAggregation<QuestParty>
{
    public QuestParty Create(QuestStarted @event) => new() { Name = @event.Name };
    public void Apply(QuestParty view, MembersJoined @event) => view.Members.Fill(@event.Members);
}

public class QuestParty
{
    public Guid Id { get; set; }
    public List<string> Members { get; set; } = new();
    public string Name { get; set; }
}

public record MembersJoined(int Day, string Location, string[] Members);

public record QuestStarted(string Name);

MediatR用于将命令委派给命令处理程序

第一次
这涉及到我的.net应用程序中的以下设置:

builder.Services.AddMarten(x =>
{
    x.Connection(builder.Configuration.GetConnectionString("Marten")!);
    x.Projections.Add<QuestPartyProjection>(ProjectionLifecycle.Async);
})
    .OptimizeArtifactWorkflow(TypeLoadMode.Static)
    .AddAsyncDaemon(DaemonMode.HotCold)
    .UseLightweightSessions()
    .InitializeWith();;
builder.Services.AddMediatR(typeof(StartHandler));
c3frrgcw

c3frrgcw1#

我使用xunit(用于声明和执行测试)、TestContainers(用于动态运行Docker PostgreSQL容器)和Microsoft.AspNetCore.Mvc.Testing创建了集成测试,Microsoft.AspNetCore.Mvc.Testing用于在内存中引导应用程序进行端到端功能测试。
我的csproj有这些包引用(以及对要测试的项目的引用):

<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Testcontainers" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.0" />

首先,由于mvc.testing需要一个公共入口点,我将其添加到program.cs中

public partial class Program { } // Expose the Program class for use with WebApplicationFactory<T>

现在在下面的代码中,在微软的端到端测试框架上创建了一个自定义的WebApplicationFactory。每次运行都会为PostgreSQL创建一个新的Docker示例,并且应用程序配置会被指向该PostgreSQL示例的connectionstring覆盖。

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    public TestcontainerDatabase Testcontainers { get; } = 
        new TestcontainersBuilder<PostgreSqlTestcontainer>()
        .WithDatabase(new PostgreSqlTestcontainerConfiguration
        {
            Database = "db",
            Username = "postgres",
            Password = "postgres",
        })
        .Build();

    public override async ValueTask DisposeAsync()
    {
        await Testcontainers.StopAsync();
        await base.DisposeAsync();
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        Testcontainers.StartAsync().GetAwaiter().GetResult();
        builder.ConfigureHostConfiguration(configBuilder =>
        {
            configBuilder.AddInMemoryCollection(
                new Dictionary<string, string>
                {
                    ["ConnectionStrings:Marten"] = Testcontainers.ConnectionString
                }!);
        });
        return base.CreateHost(builder);
    }
}

我创建了一个抽象集成类以供重用:

public abstract class IntegrationTest : IClassFixture<CustomWebApplicationFactory<Program>>
{
    protected readonly CustomWebApplicationFactory<Program> Factory;
    protected IDocumentStore DocumentStore => Factory.Services.GetRequiredService<IDocumentStore>();

    protected IntegrationTest(CustomWebApplicationFactory<Program> factory)
    {
        Factory = factory;
    }

    /// <summary>
    /// 1. Start generation of projections
    /// 2. Wait for projections to be projected
    /// </summary>
    protected async Task GenerateProjectionsAsync()
    {
        using var daemon = await DocumentStore.BuildProjectionDaemonAsync();
        await daemon.StartAllShards();
        await daemon.WaitForNonStaleData(5.Seconds());
    }

    protected IDocumentSession OpenSession() => DocumentStore.LightweightSession();
}

现在,我的实际单元测试非常简单:
1.建立开始责任的命令
1.使用现成的httpclient将其发送到我正在运行的asp.net应用程序。JSON序列化是通过在“System.Net.Http.Json”中构建来完成的
1.等待异步生成预测
1.从PostgreSQL数据库获取投影并验证内容

public class QuestPartyTests : IntegrationTest
{
    public QuestPartyTests(CustomWebApplicationFactory<Program> factory) : base(factory)
    {
    }

    [Fact]
    public async Task Start_ShouldResultIn_Party()
    {
        // Arrange
        var command = new StartQuest(
            Guid.NewGuid(),
            Name: "Destroy the One Ring",
            Day: 1,
            Location: "Hobbiton",
            Members: new []{ "Frodo", "Sam"});

        // Act
        await Factory.CreateClient().PostAsync("quest/start", JsonContent.Create(command));

        await GenerateProjectionsAsync();

        // Assert
        await using var session = OpenSession();
        var party = await session.Query<QuestParty>().SingleAsync();

        Assert.Equal(command.Name, party.Name);
        Assert.Equal(command.Members, party.Members);
    }
}

相关问题