1

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:

  1. 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());
Sharov
  • 409
  • 7
  • 30

1 Answers1

2

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.

Chris Patterson
  • 16,928
  • 2
  • 34
  • 36
  • Thank you for reply and support! But unfortunately I have environmnet constains -- dotnet core 2.2, masstransit 6.3.2, MassTransit CastleWindsor 5.3.0. Is it possible to have workaround for your example within given constraints? – Sharov Jul 06 '20 at 18:03
  • 1
    The only line you would have to change is `Assert.That(await harness.Consumed.Any());` to use `harness.Consumed.Select().Any()` - and your Windsor library should be version 6.3.2 as well. – Chris Patterson Jul 07 '20 at 12:23
  • No, unfortunately it is not the case for me. Currently I have to use Windsor 5.0.0. Also IBusRegistrationContext cannot be resolved. – Sharov Jul 07 '20 at 12:46
  • With 6.x you need to resolve 'IRegistration' instead. – Chris Patterson Jul 07 '20 at 15:47
  • Please, see code in update. For now when I try to container.Resolve(); it throws: Castle.MicroKernel.ComponentNotFoundException : No component for supporting the service MassTransit.IRegistration was found... – Sharov Jul 07 '20 at 21:41
  • 1
    And you have the .AddMassTransit() configuration in place? Because it's added when that is called. https://github.com/MassTransit/MassTransit/blob/v6.3.2/src/Containers/MassTransit.WindsorIntegration/Registration/WindsorContainerRegistrationConfigurator.cs#L42 – Chris Patterson Jul 07 '20 at 21:54
  • True, forget this one. But now I have this: System.TypeLoadException : Method 'RegisterExecuteActivity' on type 'MassTransit.WindsorIntegration.Registration.WindsorContainerRegistrar' from assembly 'MassTransit.WindsorIntegration, Version=5.3.0.0, Culture=neutral, PublicKeyToken=b8e0e9f2f1e657fa' tried to implicitly implement an interface method with weaker type parameter constraints. – Sharov Jul 07 '20 at 22:13
  • Yeah, that's why you don't mix versions of MassTransit assemblies. – Chris Patterson Jul 07 '20 at 22:15
  • So what are my options? To what version can I downgrade masstransit? – Sharov Jul 07 '20 at 23:15
  • @ChrisPatterson my test is now very similar to this answer (thanks!) except using ASP.NET Core DI. I find that messages are only actually delivered during the `await harness.Stop();`. Therefore the assertion just above that, fails. Any ideas what I can check? – Iain Apr 07 '21 at 00:00