Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing a MassTransit Consumer using the InMemoryTestFixture

Wanting to design my test around a MassTransit Consumer where i can send the consumers messages with a variety of content. Base on the content of the message the consumer will "do work" and relay a messages.

The problem i have is when running two of these test, in separate test fixtures, there seems to be something interfering with the second test. But run individually each test runs successfully.

After looking through the MassTransit Test project i have come up with some example test code to demonstrate the problem i'm having.

[TestFixture]
public class PingPongMessageTestFixture : InMemoryTestFixture
{
    private PongConsumer _pongConsumer;
    protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator)
    {
        _received = Handled<IPongMessage>(configurator);
    }

    protected override void PreCreateBus(IInMemoryBusFactoryConfigurator configurator)
    {
        var _pingConsumer = new PingConsumer();
        _pongConsumer = new PongConsumer();
        configurator.ReceiveEndpoint("test_ping_queue", e =>
        {
            e.Consumer(() => _pingConsumer);
        });

        configurator.ReceiveEndpoint("test_pong_queue", e =>
        {
            e.Consumer(() => _pongConsumer);
        });
    }

    Task<ConsumeContext<IPongMessage>> _received;

    [Test]
    public async Task test_how_to_test_consumers()
    {
        await Bus.Publish<IPingMessage>(new { MessageId = 100 });
        await _received;

        Assert.IsTrue(_pongConsumer.hitme);
        Assert.AreEqual(100, _pongConsumer.pongMessage.MessageId);
    }

    public class PingConsumer : IConsumer<IPingMessage>
    {
        public Task Consume(ConsumeContext<IPingMessage> context)
        {
            context.Publish<IPongMessage>(new { context.Message.MessageId });
            return Task.CompletedTask;
        }
    }

    public class PongConsumer : IConsumer<IPongMessage>
    {
        internal bool hitme;
        internal IPongMessage pongMessage;
        public Task Consume(ConsumeContext<IPongMessage> context)
        {
            hitme = true;
            pongMessage = context.Message;
            return Task.CompletedTask;
        }
    }

    public interface IPingMessage
    {
        int MessageId { get; set; }
    }

    public interface IPongMessage
    {
        int MessageId { get; set; }
    }
}

this test will send a message to the ping consumer which itself will send a message to the pong consumer.

This by itself works and tests that the ping consumer will send a pong message. In a real life scenario the "ping" consumer to send Update messages to another service and the pong consumer is just a test consumer used with the tests.

If i have a second test fixture, which for this questions is very similar, it will fail when both test are run together. though individually it will pass.

The test does the same thing

[TestFixture]
public class DingDongMessageTestFixture : InMemoryTestFixture
{
    private DongConsumer _pongConsumer;
    protected override void ConfigureInMemoryReceiveEndpoint(IInMemoryReceiveEndpointConfigurator configurator)
    {
        _received = Handled<IDongMessage>(configurator);
    }

    protected override void PreCreateBus(IInMemoryBusFactoryConfigurator configurator)
    {
        var _dingConsumer = new DingConsumer();
        _dongConsumer = new DongConsumer();
        configurator.ReceiveEndpoint("test_ding_queue", e =>
        {
            e.Consumer(() => _dingConsumer);
        });

        configurator.ReceiveEndpoint("test_dong_queue", e =>
        {
            e.Consumer(() => _dongConsumer);
        });
    }

    Task<ConsumeContext<IDongMessage>> _received;

    [Test]
    public async Task test_how_to_test_consumers()
    {
        await Bus.Publish<IDingMessage>(new { MessageId = 100 });
        await _received;

        Assert.IsTrue(_pongConsumer.hitme);
        Assert.AreEqual(100, _pongConsumer.pongMessage.MessageId);
    }

    public class DingConsumer : IConsumer<IDingMessage>
    {
        public Task Consume(ConsumeContext<IDingMessage> context)
        {
            context.Publish<IDongMessage>(new { context.Message.MessageId });
            return Task.CompletedTask;
        }
    }

    public class DongConsumer : IConsumer<IDongMessage>
    {
        internal bool hitme;
        internal IDongMessage pongMessage;
        public Task Consume(ConsumeContext<IDongMessage> context)
        {
            hitme = true;
            pongMessage = context.Message;
            return Task.CompletedTask;
        }
    }

    public interface IDingMessage
    {
        int MessageId { get; set; }
    }

    public interface IDongMessage
    {
        int MessageId { get; set; }
    }
}

Is this a good approach for testing a Masstransit consumers?

If so, do i need to reset the InMemoryTestFixture, somehow, per test fixture?

like image 502
theHaggis Avatar asked Oct 24 '17 12:10

theHaggis


People also ask

How to test MASSTRANSIT in-memory consumers?

To test these consumers you can use MassTransit in-memory test harness; using System. Threading. Tasks; using MassTransit. Testing; using NUnit. Framework; var consumerHarness = harness. Consumer < SubmitOrderConsumer > (); await harness. Start (); await harness. Stop ();

Why are MassTransit unit tests so complicated?

This inversion of control, combined with asynchronous execution, can complicate unit tests. To make it easy, MassTransit includes test harnesses to create unit tests that run entirely in-memory but behave close to an actual message broker. In fact, the included memory-based messaging fabric was inspired by RabbitMQ exchanges and queues.

How to test dependency injection in my MassTransit consumers?

I’m using Dependency Injection in my MassTransit consumers. To test these consumers you can use MassTransit in-memory test harness; using System. Threading. Tasks; using MassTransit. Testing; using NUnit. Framework; var consumerHarness = harness. Consumer < SubmitOrderConsumer > (); await harness. Start (); await harness. Stop ();

What is MassTransit?

MassTransit is a framework, and follows the Hollywood principle – don't call us, we'll call you. This inversion of control, combined with asynchronous execution, can complicate unit tests.


1 Answers

In your test fixtures, I don't believe there should be any conflict, but because of the interaction with NUnit, there may be something of which I'm unaware because of the base class inheritance that's being used.

If you use the InMemoryTestHarness directly (the same functionality as the text fixtures, but without any testing framework dependency) I would expect that you should not experience any interactions between two simultaneously executing tests.

Your approach is the way it should be done, but again, I'd suggesting using the InMemoryTestHarness instead of the fixture.

An example test is linked: https://github.com/MassTransit/MassTransit/blob/master/src/MassTransit.Tests/Testing/ConsumerTest_Specs.cs

like image 154
Chris Patterson Avatar answered Dec 03 '22 00:12

Chris Patterson