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
我们在 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