Masstransit unti tests problem and ISendEndpointProvider within DI - aspnetboilerplate

I want to unit test services which uses Masstransit(rmq) to send messages. According to the doc, there is InMemoryTestHarness class for this. But I can't figure out how can I use it in my scenario.
I use AbpBoilerplate framework, so first of all I need to register Masstransit within Castle Windsor. For test module I do it smth like this:
var harness = new InMemoryTestHarness();
harness.Start().Wait(TimeSpan.FromSeconds(1));//harness.Bus is not null now
IocManager.IocContainer.Register(Component.For<InMemoryTestHarness>().Instance(harness));
IocManager.IocContainer.Register(Component.For<IBus, IBusControl>().Instance(harness.Bus));
Service I want to test is following:
public class ProcessingService
{
private readonly ProjectService _projectService;
//NOTE previously there was IBus interface...
private readonly ISendEndpointProvider _provider;
public ProcessingService(ISendEndpointProvider provider, [NotNull] ProjectService projectService )
{
_ProjectService = ProjectService ?? throw new ArgumentNullException(nameof(ProjectService));
_provider = provider;
}
public async Task ProcessFileAsync([NotNull] fileInput FileInput,
[NotNull] IUserProcessingSettings userProcessingSettings)
{
if (fileInput == null) throw new ArgumentNullException(nameof(fileInput));
if (userProcessingSettings == null) throw new ArgumentNullException(nameof(userProcessingSettings));
var entity = await _projectService.GetSomeEntityAsync();
ProcessFileCommand msg = new ProcessFileCommand(entity);
await _provider.Send(msg).ConfigureAwait(false);
}
}
The first problem with code above is that for now if I use ISendEndpointProvider instead of IBus as suggeted in the doc it is simply cannot resolve dependecies, no ISendEndpointProvider registered. And I'm not quite understand how to handle it within InMemoryTestHarness abstraction...
Here is the test method and consumer class:
[Fact]
public async Task Simple_Masstranist_Consumer_Test1()
{
//it was injected and started in ABP test module
var harness = Resolve<InMemoryTestHarness>();
//NOTE which one is correct option -- handler or consumer?
var consumerHarness = harness.Consumer<MyConsumer>();
// await harness.SubscribeHandler<MyConsumer>().ConfigureAwait(false);
try
{
var processingService = Resolve<ProcessingService>();
var emptyFileImput = FileInput.EmptyFileInput(1);
await ProcessingService.ProcessFileAsync(emptyFileImput).ConfigureAwait(false);
//NOTE test hangs on this command
consumerHarness.Consumed.Select<ProcessFileCommand>().Any().ShouldBeTrue();
// the consumer publish the event
harness.Published.Select<ProcessFileCommand>().Any().ShouldBeTrue();
}
finally
{
//already started...
await harness.Stop().ConfigureAwait(false);
}
}
public class MyConsumer : IConsumer<ProcessFileCommand>
{
public Task Consume(ConsumeContext<ProcessFileCommand> context)
{
context.Message.ShouldNotBeNull();
context.Message.EntityId.ShouldBe(1);
context.Message.FilePath.ShouldBe("some_path");
return Task.FromResult(true);
}
}
My expectations for InMemoryTestHarness is that it is a wrapper of InMemory bus suitable for tests, so when my service send some message, I expect that it would be reflected in proper structures of InMemoryTestHarness. When I've used IBus instead of ISendEndpointProvider test code simply hang on
consumerHarness.Consumed.Select().Any().ShouldBeTrue(); line of code. It seems that it blocks for some event which will neven occur.
So, my questions are:
Is it correct to use InMemoryTestHarness to mock message queue in my scenario, maybe I should configure manually InMemoryBus for that?
2)If I want to use ISendEndpointProvider within DI, what should I register?
Thanks in advance
Update:
var testHarness = new InMemoryTestHarness();
IocManager.IocContainer.Register(Component.For<InMemoryTestHarness>().UsingFactoryMethod(kernel =>
{
var busRegistrationContext = kernel.Resolve<IRegistration>();
testHarness.OnConfigureInMemoryBus +=
configurator => configurator.ConfigureEndpoints(busRegistrationContext);
return testHarness;
}).LifestyleSingleton());

You can use the built-in container configuration with the test harness, I've included an example of how to do it with Castle Windsor below. This will ensure all required types are registered in the container, including IBus, ISendEndpointProvider, and IPublishEndpoint.
You do not need to start the bus, the harness starts and creates it for you.
Also, do not access IBusControl prior to resolving and starting the test harness, or the BusControl property on the harness will not be initialized, returning null.
[Test]
public async Task Should_startup_the_container_with_the_harness()
{
var container = new WindsorContainer()
.Register(Component.For<InMemoryTestHarness>().UsingFactoryMethod(kernel =>
{
var testHarness = new InMemoryTestHarness();
var busRegistrationContext = kernel.Resolve<IBusRegistrationContext>();
testHarness.OnConfigureInMemoryBus += configurator => configurator.ConfigureEndpoints(busRegistrationContext);
return testHarness;
}).LifestyleSingleton())
.AddMassTransit(x =>
{
x.AddConsumer<PingConsumer>();
x.AddBus(context => context.GetRequiredService<InMemoryTestHarness>().BusControl);
});
var harness = container.Resolve<InMemoryTestHarness>();
await harness.Start();
try
{
var bus = container.Resolve<IBus>();
await bus.Publish(new PingMessage());
Assert.That(await harness.Consumed.Any<PingMessage>());
}
finally
{
await harness.Stop();
container.Dispose();
}
}
This won't setup Consumer, Saga, or other harnesses, but it will configure endpoints on the test harness.

Related

How to unit test if consumer ran successfully in MassTransit

Im trying to write a unit test that should validate if a consumer ran or not.
But for some reason the consumer never executes.
I have created a more simplified version of the problem using the Getting-Started-sample of MassTransit.
This is "my" code:
[TestClass]
public class UnitTest1
{
private ITestHarness _testHarness;
[TestInitialize]
public void Initialize()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransitTestHarness(busRegistrationConfigurator =>
{
busRegistrationConfigurator.AddConsumer<MessageConsumer>();
});
var serviceProvider = serviceCollection.BuildServiceProvider();
_testHarness = serviceProvider.GetRequiredService<ITestHarness>();
}
[TestMethod]
public async Task TestMethod1()
{
await _testHarness.Bus.Publish(new Message { Text = "Hello, world!" });
(await _testHarness.Published.Any<Message>()).Should().BeTrue();
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
}
}
The following assertion:
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
Fails, since it always returns False.
I guess that I somehow need to await the consumer to execute? And I might be missing even more..
Been having a look at the documentations of MassTransit regarding testing, but Im not sure if the sample provided in the docs apply to this scenario (?), since the sample provided involves a request and response.
Would very much appreciate any help!
You're missing one line:
await _testHarness.Start();
Of course, you'll need to be sure and Dispose the container to ensure the bus is stopped and cleaned up.

MassTransit failing to consume

I'm switching some code which uses MassTransit (v7.2.2 on .NET 5) to use a more declarative format (and away from multiple calls to ReceiveEndpoint()) and ideally to using ConsumerDefinitions for the configuration (though not part of this example for simplicity), along with some Dependency Injection with Quartz.NET (yanked from this example, though it running 3.3.3), in doing so I find now that my Consumers are not consuming, despite messages being sent and examples referenced. Take the following standing up of the MassTransit service:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddBus(provider => Bus.Factory.CreateUsingInMemory(cfg =>
{
//cfg.AutoStart = true; //No change when on
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(provider);
}));
mt.AddConsumer<TheMessageConsumer>();
services.AddMediator(cfg =>
{
cfg.AddConsumer<TheMessageConsumer>();
});
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
var busControl = host.Services.GetService<IBusControl>();
busControl.Start(); //Just in case
var message = new TheMessage() { Message = $"<Message-{DateTime.Now.ToLongTimeString()}>" };
Console.WriteLine($"Sending: {message.Message}");
await busControl.Publish(message);
host.Run();
For note, the breaking out of the message sending here is to simplify my repro, as in my full code base, it's being sent by a Quartz fired job.
For this example, the message & receiver are also quite simple:
public class TheMessage
{
public string Message { get; set; }
}
public class TheMessageConsumer : IConsumer<TheMessage>
{
public Task Consume(ConsumeContext<TheMessage> context)
{
Console.WriteLine($"Message received: {context.Message.Message}");
return Task.CompletedTask;
}
}
The bus is started, either in the case I explicitly start it, the AutoStart flag is set, or the MassTransitHostedService does it, yet the message doesn't get received. Ditto if I have the full example with Quartz firing off a job with messages much later.
Can someone suggest what I am missing?
The code you posted is seriously a hodgepodge of snippets, none of which make any sense when used together. For example, AddBus is deprecated, and mediator has no business being in that project at all.
I'd suggest using one of the MassTransit Templates to create a new project from scratch (you may need to up the NuGet versions to 7.2.2).
Watch this video which explains the templates and how to use them.
With your comment, and updated question, you really only need the following:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddConsumer<TheMessageConsumer>();
mt.UsingInMemory((context,cfg) =>
{
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(context);
}));
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
await host.RunAsync();
If you want to test it, and send messages in the same process, simply add a BackgroundService (after AddMassTransitHostedService) to publish your messages.
You shouldn't publish until after the bus has been started.

How to use HttpContext inside Task.Run

There is some posts explain how to tackle, but couldnt help me much..
Logging Request/Response in middleware, it works when use 'await' with Task.Run() but since its awaited current operation to complete there is performance issue.
When I remove await as below, it runs fast but not logging anything, since HttpContext instance not available to use inside parallel thread
public class LoggingHandlerMiddleware
{
private readonly RequestDelegate next;
private readonly ILoggerManager _loggerManager;
public LoggingHandlerMiddleware(RequestDelegate next, ILoggerManager loggerManager)
{
this.next = next;
_loggerManager = loggerManager;
}
public async Task Invoke(HttpContext context, ILoggerManager loggerManager, IWebHostEnvironment environment)
{
_ = Task.Run(() =>
{
AdvanceLoggingAsync(context, _loggerManager, environment);
});
...
}
private void AdvanceLoggingAsync(HttpContext context, ILoggerManager loggerManager, IWebHostEnvironment environment, bool IsResponse = false)
{
{
context.Request.EnableBuffering(); // Throws ExecutionContext.cs not found
result += $"ContentType:{context.Request.ContentType},";
using (StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true))
{
result += $"Body:{await reader.ReadToEndAsync()}";
context.Request.Body.Position = 0;
}
loggerManager.LogInfo($"Advance Logging Content(Request)-> {result}");
}
How can I leverage Task.Run() performance with accessing HttpContext?
Well, you can extract what you need from the context, build your string you want to log, and then pass that string to the task you run.
However, firing and forgetting a task is not good. If it throws an exception, you risk of bringing down the server, or at least you will have very hard time getting information about the error.
If you are concerned about the logging performance, better add what you need to log to a message queue, and have a process that responds to new messages in the queue and logs the message to the log file.

Dependency injection in Masstransit routing slip Activity

I working on a project using masstransit Courier. I am trying to inject some service dependencies into my Activities but this is not working. Can someone help how to use DI in MT activity. I could not find any working example with Activity implementation and DI
In my below code I am trying to inject IMyservice into my activity. But my activity constructor is never executing. Am I doing something wrong here?
public class DownloadImageActivity :
Activity<DownloadImageArguments, DownloadImageLog>
{
public DownloadImageActivity (IMyService service)
{
_service=service;
}
Task<ExecuteResult> Execute(ExecutionContext<DownloadImageArguments> context);
Task<CompensationResult> Compensate(CompensateContext<DownloadImageLog> context);
}
my service program code ActivityService.cs
_busControl = Bus.Factory.CreateUsingRabbitMq(x =>
{
IRabbitMqHost host = x.Host(new Uri(ConfigurationManager.AppSettings["RabbitMQHost"]), h =>
{
h.Username("guest");
h.Password("guest");
});
x.ReceiveEndpoint(host, ConfigurationManager.AppSettings["completepitaskqueue"], e =>
{
e.PrefetchCount = 100;
e.ExecuteActivityHost<CompletePiTaskActivity, ComplePiTaskArguments>(Program.Container);
// e.ExecuteActivityHost(
// DefaultConstructorExecuteActivityFactory<CompletePiTaskActivity, ComplePiTaskArguments>.ExecuteFactory, c => c.UseRetry(r => r.Immediate(5)));
});
My program.cs
public static IContainer Container;
static int Main(string[] args)
{
ConfigureLogger();
Container = builder.Build();
ActivityHelper.Container = Container;
// MassTransit to use Log4Net
Log4NetLogger.Use();
return (int)HostFactory.Run(x => x.Service<ActivityService>());
It depends upon which container you are using, some are more supported than others (out of the box, anyway, any should work).
For example, to configure a receive endpoint with an activity using Autofac, you would specify:
cfg.ReceiveEndpoint(host, "execute-activity", e =>
{
e.ExecuteActivityHost<TActivity, TArguments>(container);
});
Where container is either the builder context or the container. This will register the appropriate scope providers and activity factories for your activity.
This uses the extension method: https://github.com/MassTransit/MassTransit/blob/develop/src/Containers/MassTransit.AutofacIntegration/AutofacExtensions.cs#L300

How To: Caliburn.Micro.Autofac and Windows Phone

Is there and example, tutorial or anything that shows how to use Caliburn.Micro.Autofac with Windows Phone?
I created a basic application with Caliburn.Micro only, and that runs fine. Then I decided to use Caliburn.Micro.Autofac, so I derived my Bootstrapper from Caliburn.Micro.Autofac.AutofacBootstrapper and called base.Configure() inside the Bootstrapper Configure() method. Now wen I ran the application I get "The type 'AppBootstrapper' was not found." exception.
Appreciate any help.
This is the bootstrapper I wrote for a WP7 project. It's based on Caliburn.Micro.Autofac.AutofacBootstrapper but fixes some bugs.
public class AppBootstrapper : PhoneBootstrapper
{
private IContainer container;
protected void ConfigureContainer(ContainerBuilder builder)
{
// put any custom bindings here
}
#region Standard Autofac/Caliburn.Micro Bootstrapper
protected override void Configure()
{
// configure container
var builder = new ContainerBuilder();
// register phone services
var caliburnAssembly = AssemblySource.Instance.Union(new[] { typeof(IStorageMechanism).Assembly }).ToArray();
// register IStorageMechanism implementors
builder.RegisterAssemblyTypes(caliburnAssembly)
.Where(type => typeof(IStorageMechanism).IsAssignableFrom(type)
&& !type.IsAbstract
&& !type.IsInterface)
.As<IStorageMechanism>()
.SingleInstance();
// register IStorageHandler implementors
builder.RegisterAssemblyTypes(caliburnAssembly)
.Where(type => typeof(IStorageHandler).IsAssignableFrom(type)
&& !type.IsAbstract
&& !type.IsInterface)
.As<IStorageHandler>()
.SingleInstance();
// The constructor of these services must be called
// to attach to the framework properly.
var phoneService = new PhoneApplicationServiceAdapter(RootFrame);
var navigationService = new FrameAdapter(RootFrame, false);
builder.Register<IPhoneContainer>(c => new AutofacPhoneContainer(c)).SingleInstance();
builder.RegisterInstance<INavigationService>(navigationService).SingleInstance();
builder.RegisterInstance<IPhoneService>(phoneService).SingleInstance();
builder.Register<IEventAggregator>(c => new EventAggregator()).SingleInstance();
builder.Register<IWindowManager>(c => new WindowManager()).SingleInstance();
builder.Register<IVibrateController>(c => new SystemVibrateController()).SingleInstance();
builder.Register<ISoundEffectPlayer>(c => new XnaSoundEffectPlayer()).SingleInstance();
builder.RegisterType<StorageCoordinator>().AsSelf().SingleInstance();
builder.RegisterType<TaskController>().AsSelf().SingleInstance();
// allow derived classes to add to the container
ConfigureContainer(builder);
// build the container
container = builder.Build();
// start services
container.Resolve<StorageCoordinator>().Start();
container.Resolve<TaskController>().Start();
// add custom conventions for the phone
AddCustomConventions();
}
protected override object GetInstance(Type service, string key)
{
object instance;
if (string.IsNullOrEmpty(key))
{
if (container.TryResolve(service, out instance))
return instance;
}
else
{
if (container.TryResolveNamed(key, service, out instance))
return instance;
}
throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? service.Name));
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.Resolve(typeof(IEnumerable<>).MakeGenericType(service)) as IEnumerable<object>;
}
protected override void BuildUp(object instance)
{
container.InjectProperties(instance);
}
private static void AddCustomConventions()
{
ConventionManager.AddElementConvention<Pivot>(Pivot.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Pivot.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Pivot.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
ConventionManager.AddElementConvention<Panorama>(Panorama.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding =
(viewModelType, path, property, element, convention) =>
{
if (ConventionManager
.GetElementConvention(typeof(ItemsControl))
.ApplyBinding(viewModelType, path, property, element, convention))
{
ConventionManager
.ConfigureSelectedItem(element, Panorama.SelectedItemProperty, viewModelType, path);
ConventionManager
.ApplyHeaderTemplate(element, Panorama.HeaderTemplateProperty, viewModelType);
return true;
}
return false;
};
}
#endregion
}
EDIT I have created a fork of Caliburn.Micro.Autofac and fixed the issue on GitHub. Hopefully the pull request will be accepted and this will become part of the main repository.
For now, you can access the bootstrapper, and AutofacPhoneContainer from here - https://github.com/distantcam/Caliburn.Micro.Autofac/tree/master/src/Caliburn.Micro.Autofac-WP7
I have implemented a proper version (in my opinion) of Caliburn.Micro.Autofac for Windows Phone. You can download it and test project from my blog. The blog post is in Russian but you'll find the link to ZIP file in the top of the post. The code is too big to post here, so please take from the blog. I've send this to David Buksbaum (the author of Caliburn.Micro.Autofac). Hope he will incorporate it into his code base soon.
UPDATE
What is fixed:
Components realizing IPhoneService and INavigationService services must be instantiated before registering in container.
Realized component implementing IPhoneContainer. Without it you can't use Autofac in Caliburn.Micro.

Resources