I want to be able to send message from a unit test to an ASP.Net Core application using the in-memory implementation of MassTransit. The in-memory implementation is important as the actual application uses ActiveMQ but we want to replace that during testing with the in-memory implementation.
I started creating a new ASP.Net Core API project from VS2019 using .NET 5 and added MassTransit to Startup
as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplicationWithMassTransit", Version = "v1" });
});
services.AddMassTransit(x =>
{
x.AddConsumer<MessageConsumer>();
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService(true);
}
The MessageConsumer
looks like this:
public class MessageConsumer : IConsumer<Message>
{
readonly ILogger<MessageConsumer> _logger;
public MessageConsumer(ILogger<MessageConsumer> logger)
{
_logger = logger;
}
public Task Consume(ConsumeContext<Message> context)
{
_logger.LogInformation("Received Text: {Text}", context.Message.Text);
return Task.CompletedTask;
}
}
And the Message
looks like this:
public class Message
{
public string Text { get; set; }
}
When I inject IPublishEndpoint
into the WeatherForecastController
and send a message when a GET
is done using:
await _endpoint.Publish(new Message {Text = "Test message"});
everything works as expected.
But now I want to send a message from a unit test to the application. So I created an XUnit test project and referenced the ASP.Net API project. The test class looks as follows:
public class UnitTest1
: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public UnitTest1(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task SendMassTransitMessage()
{
var client = _factory.CreateClient();
using var scope = _factory.Services.CreateScope();
var bus = scope.ServiceProvider.GetRequiredService<IBus>();
await bus.Publish(new Message {Text = "Test message from test"});
}
}
When I run the test, it succeeds, but the consumer is not called. In the Debug window I get the following stacktrace:
MassTransit: Information: Configured endpoint Message, Consumer: WebApplicationWithMassTransit.MessageConsumer
MassTransit: Information: Bus started: loopback://localhost/
MassTransit.ReceiveTransport: Error: R-FAULT loopback://localhost/Message af0f0000-50b6-c8f7-16d4-08d9affc5a91 WebApplicationWithMassTransit.Message WebApplicationWithMassTransit.MessageConsumer(00:00:00.0072326)
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.CreateScope(IServiceProvider provider)
at MassTransit.ExtensionsDependencyInjectionIntegration.ScopeProviders.DependencyInjectionConsumerScopeProvider.MassTransit.Scoping.IConsumerScopeProvider.GetScope[TConsumer,T](ConsumeContext`1 context)
at MassTransit.Scoping.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next)
at MassTransit.Pipeline.Filters.ConsumerMessageFilter`2.GreenPipes.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next)
MassTransit: Information: Bus stopped: loopback://localhost/
Is it possible to send messages from a unit test like this? What could I be doing wrong? I tried resolving IBus
, IBusControl
and IPublishEndpoint
in several ways, with and without creating a scope.
CodePudding user response:
You are awaiting the publish, but at that point, the test exits and starts tearing down the application. You would need to wait until the consumer has completed, since it's called asynchronously. When using the in-memory test harness, there are monitors that can be used to wait until there is no bus activity, but I have no idea how you'd do that in your scenario above.
How are you expecting to assert that the consumer actually completed the work?
To prove this, add a simple await Task.Delay(1000)
and see if the consumer executes.