Search code examples
c#unit-testingrabbitmqmasstransitnsubstitute

How to mock MassTransit Header values in unit test using NSubstitute


I'm trying to create a unit test that will test my MassTransit (RabbitMq) consumer. My consumer is expected a bus header key called UserName. I've simplified my consumer class as follows. I need to find a way to mock the bus header data otherwise the test will always raise an exception when it executes context.Headers.TryGetHeader("UserName", out object value)

I'm using the NSubstitute mocking library. So how do I mock the ConsumeContext<MyDataObject> type, and set a mock header value? I prefer a solution with NSubstitute

public class MyDataObjectCommand
{
    public string MyProperty { get; set; }
}

// Consumer class
public class MyConsumer : IConsumer<MyDataObjectCommand>
{
    private readonly IHubContext<MessageHub> _hubContext;

    public MyConsumer(IHubContext<MessageHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task Consume(ConsumeContext<MyDataObjectCommand> context)
    {
        var myProperty = context.Message.MyProperty;
        var userName = TryGetHeader(context, "UserName");
        DoSomethingWith(_hubContext, myProperty, userName);
    }

    private string TryGetHeader(ConsumeContext<MyDataObjectCommand> context, string key, bool errorOnNull = true)
    {
        if (context.Headers.TryGetHeader(key, out object value))
        {
            return Convert.ToString(value);
        }

        if (errorOnNull)
        {
            throw new MyConsumerException(_reportName, $"{key} is not found", _hubContext);
        }

        return null;
    }
}


// Unit Test (I'm using XUnit)
public class MyConsumerTests
{
    private readonly IHubContext<MessageHub> _hubContext;

    public MyConsumerTests()
    {
       _hubContext = Substitute.For<IHubContext<MessageHub>>();
    }

    [Fact]
    public async Task ShouldConsume()
    {
        // arrange
        var mockDataObject = new MyDataObjectCommand() { MyProperty = "x" };

        var harness = new InMemoryTestHarness();
        harness.Consumer(() => new MyConsumer(_hubContext));

        // I need to mock header values on ConsumeContext<MyDataObjectCommand> object here
        // but how do I do it using NSubstitute?
        // context.Headers["UserName"] = "Bob";  ??

        await harness.Start();
        try
        {
            
            // act
            await harness.InputQueueSendEndpoint.Send(mockDataObject);

            // assert
            Assert.True(await harness.Consumed.Any<MyDataObjectCommand>());
            Assert.False(await harness.Consumed.Any<Fault<MyDataObjectCommand>>());
            //Assert.Equal(context.Header["UserName"], "Bob"); How do I do the assertion??
        }
        finally
        {
            await harness.Stop();
        }
    }
}

Solution

  • You don’t mock header values when using MassTransit, you just have to set them when you’re sending the message using the test harness. There is absolutely zero need for a mocking framework.

    await harness.InputQueueSendEndpoint.Send(mockDataObject, 
        context => context.Headers.Set(“UserName”, “Bob”));
    

    The header will then be available on the ConsumeContext when the message is delivered to the consumer.