Build/Test 验证缺少 MediatR 中 query/commands 的实现

Build/Test verification for missing implementations of query/commands in MediatR

我们在 LoB 应用程序中大量使用 MediatR,我们在其中使用命令和查询模式。 通常,为了继续开发,我们首先发出命令和查询,因为它们是简单的 POCO。

这有时会导致忘记创建实际的命令 handler/query 处理程序。由于如果 query/command 实际上有一个实现,则没有编译时验证,我想知道在能够合并到 master 之前,查看是否有实现并在没有实现时抛出错误的最佳方法是什么.

到目前为止我的想法: 创建两个测试,一个用于查询,一个用于命令,扫描所有程序集以查找 IRequest<TResponse> 的实现,然后扫描程序集以查找 IRequestHandler<TRequest, TResponse>

的关联实现

但这仍然需要首先执行测试(这发生在构建管道中),这仍然取决于开发人员手动执行测试(或配置 VS 在编译后执行)。

我不知道是否有针对此的编译时解决方案,即使那是个好主意?

我们已经进行了测试(以及构建时)验证; 在这里分享实际测试的代码,我们每个域项目都有一次。 中介模块包含我们的 query/command(handler) 注册,基础设施模块包含我们的查询处理程序;

public class MissingHandlersTests
{
    [Fact]
    public void Missing_Handlers()
    {
        List<Assembly> assemblies = new List<Assembly>();

        assemblies.Add(typeof(MediatorModules).Assembly);
        assemblies.Add(typeof(InfrastructureModule).Assembly);

        var missingTypes = MissingHandlersHelpers.FindUnmatchedRequests(assemblies);

        Assert.Empty(missingTypes);
    }
}

帮手class;

public class MissingHandlersHelpers
{      
    public static IEnumerable<Type> FindUnmatchedRequests(List<Assembly> assemblies)
    {
        var requests = assemblies.SelectMany(x => x.GetTypes())
            .Where(t => t.IsClass && t.IsClosedTypeOf(typeof(IRequest<>)))
            .ToList();

        var handlerInterfaces = assemblies.SelectMany(x => x.GetTypes())
            .Where(t => t.IsClass && (t.IsClosedTypeOf(typeof(IRequestHandler<>)) || t.IsClosedTypeOf(typeof(IRequestHandler<,>))))
            .SelectMany(t => t.GetInterfaces())
            .ToList();

        List<Type> missingRegistrations = new List<Type>();
        foreach(var request in requests)
        {
            var args = request.GetInterfaces().Single(i => i.IsClosedTypeOf(typeof(IRequest<>)) && i.GetGenericArguments().Any() && !i.IsClosedTypeOf(typeof(ICacheableRequest<>))).GetGenericArguments().First();

            var handler = typeof(IRequestHandler<,>).MakeGenericType(request, args);

            if (handler == null || !handlerInterfaces.Any(x => x == handler))
                missingRegistrations.Add(handler);

        }
        return missingRegistrations;
    }
}

如果您使用的是 .Net Core,则可以 Microsoft.AspNetCore.TestHost 创建一个您的测试可以命中的端点。作品类型如下:

var builder = WebHost.CreateDefaultBuilder()
                .UseStartup<TStartup>()
                .UseEnvironment(EnvironmentName.Development)
                .ConfigureTestServices(
                    services =>
                    {
                        services.AddTransient((a) => this.SomeMockService.Object);
                    });

            this.Server = new TestServer(builder);
            this.Services = this.Server.Host.Services;
            this.Client = this.Server.CreateClient();
            this.Client.BaseAddress = new Uri("http://localhost");

所以我们模拟任何 http 调用(或任何其他我们想要的东西)但是 真正的 启动被调用。

我们的测试将是这样的:

public SomeControllerTests(TestServerFixture<Startup> testServerFixture)
        : base(testServerFixture)
        {
        }

        [Fact]
        public async Task SomeController_Returns_Titles_OK()
        {
            var response = await this.GetAsync("/somedata/titles");

            response.StatusCode.Should().Be(HttpStatusCode.OK);
            var responseAsString = await response.Content.ReadAsStringAsync();
            var actualResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<string>>(responseAsString);

            actualResponse.Should().NotBeNullOrEmpty();
            actualResponse.Should().HaveCount(20);
        }

因此,当此测试运行时,如果您尚未注册您的处理程序,它将失败!我们用它来断言我们需要什么(添加数据库记录,响应我们期望的等等)但这是一个很好的副作用,忘记注册你的处理程序会在测试阶段被捕获!

https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api