rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.26k stars 353 forks source link

How to mock MessageContext.Current in outgoing step? #1143

Closed andrescardenash closed 3 months ago

andrescardenash commented 4 months ago

I'm facing a challenge with mocking MessageContext.Current in an outgoing step in Rebus. I have a piece of code that looks like this:

    public class NewRelicTraceSend : IOutgoingStep
    {
        public async Task Process(OutgoingStepContext context, Func<Task> next)
        {
            var messageContext = MessageContext.Current;
            var transaction = NewRelic.GetAgent().CurrentTransaction;

            if (messageContext?.Headers != null)
            {
                transaction.InsertDistributedTraceHeaders(messageContext.Headers, (dictionary, key, value) =>
                {
                    dictionary.Add(key, value);
                });
            }

            try
            {
                await next();
            }
            catch (Exception error)
            {
                Log.Error(error, "Fatal error sending message.");
                NewRelic.NoticeError(error, messageContext.Headers);

                throw;
            }
      }

I've tried to follow the steps to create tests for incoming and outgoing steps(link), but I'm still unable to mock MessageContext.Current successfully. Can you provide guidance or examples on how to properly mock MessageContext.Current in this context? the idea is to create a test to validate some additional headers that InsertDistributedTraceHeaders is appending but MessageContentext.Current is always null.

Test:

        [TestMethod]
        public async Task Process_WithMessageContextHeaders_AddsHeadersToTransaction()
        {
            using (var activator = new BuiltinHandlerActivator())
            {
                var step = new NewRelicTraceSend();
                var network = new InMemNetwork();
                var bus = Configure
                            .With(activator)
                            .Transport(t => t.UseInMemoryTransport(network, "test-rebus-queue"))
                            .Options(o =>
                                        o.Decorate<IPipeline>(c =>
                                        {
                                            var pipeline = c.Get<IPipeline>();

                                            return new PipelineStepInjector(pipeline)
                                                .OnSend(step, PipelineRelativePosition.Before, typeof(SendOutgoingMessageStep));
                                        })
                                    )
                            .Options(o => o.LogPipeline(verbose: true))
                            .Start();

                network.CreateQueue("test-rebus-queue");

                await bus.Advanced.Routing.Send("test-rebus-queue", "testing.com");
            }
        }
mookid8000 commented 3 months ago

Hi @andrescardenash , sorry for taking so long to get back to you.

Your problem is valid, ~but there's no built-in way in Rebus to fake the current message context. It's available via the static Current property on MessageContext, but it's actually instantiated every time it gets requested from the current ambient transaction context, IF the ambient transaction context~ and I just figured out how easy it was for me to help you šŸ˜‰

With Rebus.TestHelpers 9.1.0 (which is on NuGet.org now!) you can do this:

var transportMessage = new TransportMessage(headers, body);
using var scope = new FakeMessageContextScope(transportMessage);

// this will now be available šŸŽ‰
var messageContext = MessageContext.Current;

It should make it very easy for you (and anyone else with similar problems) to write proper unit tests of their pipeline steps.

Thanks for directing my attention to the fact that this was missing.

andrescardenash commented 3 months ago

Thank you so much!!