使用 InMemoryTestHarness 时如何注入 MassTransit 的 IPublishEndpoint
How to inject MassTransit's IPublishEndpoint when using InMemoryTestHarness
概述
所以我有一个使用 MediatR 命令的现有网站 api。我正在努力将 MT 集成到其中一些中,以出于各种目的将消息发布到 RMQ。我能够很好地将它集成到我的命令中,但是在我的集成测试期间使用 InMemoryHarness 进行测试时,我 运行 遇到了问题。
详情
我有一个 NUnit 测试夹具,可以像这样设置我的内存线束,就像 the docs
描述。
[OneTimeSetUp]
public async Task RunBeforeAnyTests()
{
var dockerDbPort = await DockerDatabaseUtilities.EnsureDockerStartedAndGetPortPortAsync();
var dockerConnectionString = DockerDatabaseUtilities.GetSqlConnectionString(dockerDbPort.ToString());
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables();
_configuration = builder.Build();
var services = new ServiceCollection();
// various other registration
// MassTransit Setup -- Do Not Delete Comment
_provider = services.AddMassTransitInMemoryTestHarness(cfg =>
{
// Consumers here
}).BuildServiceProvider(true);
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
await _harness.Start();
}
我还有一个 MediatR 处理程序,可能看起来像这样:
public class Handler : IRequestHandler<AddRecipeCommand, RecipeDto>
{
private readonly RecipesDbContext _db;
private readonly IMapper _mapper;
private readonly IPublishEndpoint _publishEndpoint;
public Handler(RecipesDbContext db, IMapper mapper, IPublishEndpoint publishEndpoint)
{
_mapper = mapper;
_publishEndpoint = publishEndpoint;
_db = db;
}
public async Task<RecipeDto> Handle(AddRecipeCommand request, CancellationToken cancellationToken)
{
var recipe = _mapper.Map<Recipe> (request.RecipeToAdd);
_db.Recipes.Add(recipe);
await _publishEndpoint.Publish<IRecipeAdded>(new
{
RecipeId = recipe.Id
});
await _db.SaveChangesAsync();
return await _db.Recipes
.ProjectTo<RecipeDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync(r => r.Id == recipe.Id);
}
}
我有一个看起来像这样的测试并在注入之前通过了 IPublishEndpoint
:
[Test]
public async Task can_add_new_recipe_to_db()
{
// Arrange
var fakeRecipeOne = new FakeRecipeForCreationDto { }.Generate();
// Act
var command = new AddRecipe.AddRecipeCommand(fakeRecipeOne);
var recipeReturned = await SendAsync(command);
var recipeCreated = await ExecuteDbContextAsync(db => db.Recipes.SingleOrDefaultAsync());
// Assert
recipeReturned.Should().BeEquivalentTo(fakeRecipeOne, options =>
options.ExcludingMissingMembers());
recipeCreated.Should().BeEquivalentTo(fakeRecipeOne, options =>
options.ExcludingMissingMembers());
}
但是当 MediatR 命令包含 MT 发布时,测试失败:
----> System.InvalidOperationException : Unable to resolve service for type 'MassTransit.IPublishEndpoint' while attempting to activate 'RecipeManagement.Domain.Recipes.Features.AddRecipe+Handler'.
我在哪里
同样,当 运行 针对 RMQ 的实际应用程序时,此 MediatR 命令工作正常,但似乎内存中的线束
没有引入总线以正确注入 IPublishEndpoint
我试过把类似这样的各种口味的东西带进来,但没有用。
var bus = _provider.GetRequiredService<IBus>();
services.AddSingleton<IPublishEndpoint>(bus);
我想我可以启动一个 docker 容器,其中包含 RMQ,并将其用于我的测试,但如果我可以使用内存中的线束来提高性能和简单性,那就太好了。
关于我应该如何处理这个问题有什么想法吗?
IPublishEndpoint
注册在容器中,就像所有其他与总线相关的接口一样,无论您使用 RabbitMQ 还是 InMemory 传输。
IPublishEndpoint
是范围内的,当您在解决方案中的依赖项时不确定您是否具有有效范围,但由于它似乎没有变化,我猜您可能会。但值得验证。
已更新
所以下面的方法可以工作,但是它不会让你从发布的角度对 harness 进行任何测试,这显然不理想,如果你像这样设置你的注册,它现在应该会飞:
services.AddMassTransitInMemoryTestHarness(cfg =>
{
// Consumer Registration -- Do Not Delete Comment
cfg.AddConsumer<AddToBook>();
cfg.AddConsumerTestHarness<AddToBook>();
});
_provider = services.BuildServiceProvider();
_scopeFactory = _provider.GetService<IServiceScopeFactory>();
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
await _harness.Start();
旧
所以内存数据库似乎没有办法像消费者那样注册 IPublishEndpoint
:
_provider = services.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddConsumer<AddToBook>();
cfg.AddConsumerTestHarness<AddToBook>();
}).BuildServiceProvider();
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
但我能做的只是使用 Moq
添加它的模拟。我不需要测试在这个级别发布后会发生什么,因为它是进程外操作,所以这完成了注册工作,让我的 DI 开心。
services.AddScoped(_ => Mock.Of<IPublishEndpoint>());
概述
所以我有一个使用 MediatR 命令的现有网站 api。我正在努力将 MT 集成到其中一些中,以出于各种目的将消息发布到 RMQ。我能够很好地将它集成到我的命令中,但是在我的集成测试期间使用 InMemoryHarness 进行测试时,我 运行 遇到了问题。
详情
我有一个 NUnit 测试夹具,可以像这样设置我的内存线束,就像 the docs 描述。
[OneTimeSetUp]
public async Task RunBeforeAnyTests()
{
var dockerDbPort = await DockerDatabaseUtilities.EnsureDockerStartedAndGetPortPortAsync();
var dockerConnectionString = DockerDatabaseUtilities.GetSqlConnectionString(dockerDbPort.ToString());
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables();
_configuration = builder.Build();
var services = new ServiceCollection();
// various other registration
// MassTransit Setup -- Do Not Delete Comment
_provider = services.AddMassTransitInMemoryTestHarness(cfg =>
{
// Consumers here
}).BuildServiceProvider(true);
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
await _harness.Start();
}
我还有一个 MediatR 处理程序,可能看起来像这样:
public class Handler : IRequestHandler<AddRecipeCommand, RecipeDto>
{
private readonly RecipesDbContext _db;
private readonly IMapper _mapper;
private readonly IPublishEndpoint _publishEndpoint;
public Handler(RecipesDbContext db, IMapper mapper, IPublishEndpoint publishEndpoint)
{
_mapper = mapper;
_publishEndpoint = publishEndpoint;
_db = db;
}
public async Task<RecipeDto> Handle(AddRecipeCommand request, CancellationToken cancellationToken)
{
var recipe = _mapper.Map<Recipe> (request.RecipeToAdd);
_db.Recipes.Add(recipe);
await _publishEndpoint.Publish<IRecipeAdded>(new
{
RecipeId = recipe.Id
});
await _db.SaveChangesAsync();
return await _db.Recipes
.ProjectTo<RecipeDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync(r => r.Id == recipe.Id);
}
}
我有一个看起来像这样的测试并在注入之前通过了 IPublishEndpoint
:
[Test]
public async Task can_add_new_recipe_to_db()
{
// Arrange
var fakeRecipeOne = new FakeRecipeForCreationDto { }.Generate();
// Act
var command = new AddRecipe.AddRecipeCommand(fakeRecipeOne);
var recipeReturned = await SendAsync(command);
var recipeCreated = await ExecuteDbContextAsync(db => db.Recipes.SingleOrDefaultAsync());
// Assert
recipeReturned.Should().BeEquivalentTo(fakeRecipeOne, options =>
options.ExcludingMissingMembers());
recipeCreated.Should().BeEquivalentTo(fakeRecipeOne, options =>
options.ExcludingMissingMembers());
}
但是当 MediatR 命令包含 MT 发布时,测试失败:
----> System.InvalidOperationException : Unable to resolve service for type 'MassTransit.IPublishEndpoint' while attempting to activate 'RecipeManagement.Domain.Recipes.Features.AddRecipe+Handler'.
我在哪里
同样,当 运行 针对 RMQ 的实际应用程序时,此 MediatR 命令工作正常,但似乎内存中的线束
没有引入总线以正确注入 IPublishEndpoint
我试过把类似这样的各种口味的东西带进来,但没有用。
var bus = _provider.GetRequiredService<IBus>();
services.AddSingleton<IPublishEndpoint>(bus);
我想我可以启动一个 docker 容器,其中包含 RMQ,并将其用于我的测试,但如果我可以使用内存中的线束来提高性能和简单性,那就太好了。
关于我应该如何处理这个问题有什么想法吗?
IPublishEndpoint
注册在容器中,就像所有其他与总线相关的接口一样,无论您使用 RabbitMQ 还是 InMemory 传输。
IPublishEndpoint
是范围内的,当您在解决方案中的依赖项时不确定您是否具有有效范围,但由于它似乎没有变化,我猜您可能会。但值得验证。
已更新
所以下面的方法可以工作,但是它不会让你从发布的角度对 harness 进行任何测试,这显然不理想,如果你像这样设置你的注册,它现在应该会飞:
services.AddMassTransitInMemoryTestHarness(cfg =>
{
// Consumer Registration -- Do Not Delete Comment
cfg.AddConsumer<AddToBook>();
cfg.AddConsumerTestHarness<AddToBook>();
});
_provider = services.BuildServiceProvider();
_scopeFactory = _provider.GetService<IServiceScopeFactory>();
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
await _harness.Start();
旧
所以内存数据库似乎没有办法像消费者那样注册 IPublishEndpoint
:
_provider = services.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddConsumer<AddToBook>();
cfg.AddConsumerTestHarness<AddToBook>();
}).BuildServiceProvider();
_harness = _provider.GetRequiredService<InMemoryTestHarness>();
但我能做的只是使用 Moq
添加它的模拟。我不需要测试在这个级别发布后会发生什么,因为它是进程外操作,所以这完成了注册工作,让我的 DI 开心。
services.AddScoped(_ => Mock.Of<IPublishEndpoint>());