rebus

Is it possible to add custom serialisation to Rebus FakeBus to support polymorphic types using .NET 6.0 or lower?


I have upgraded the version of Rebus.6.7.0 to 7.2.0 and Rebus.TestHelpers.7.2.0 to 8.0.0 and a unit test that was previously passing is now failing due to a desterilisation change.

The unit test creates a SagaFixture and delivers an event message SomethingFailedEvent which contains a property of type Exception. The exception type assigned is typeof(ArgumentException) with a custom message "Invalid Argument".

Within the Saga; the SomethingFailedEvent message handler publishes a new event message FailedResponse with a string property ErrorMessage set to the incoming event messages Exception.Message property.

The test then asserts that a FailedResponse message was published with a ErrorMessage property set to the SomethingFailedEvent Exception's message.

The test failure is caused by the fact that System.Text.Json is used for deserialisation which doesn't support polymorphic types (pre .NET 7.0). So when the SomethingFailedEvent message is delivered to the Saga the Exception type is desearlised to System.Exception and not the intended type ArgumentException and thus the Exception.Message property is 'Exception of type 'System.Exception' was thrown.' which causes the assert to fail.

Code snippets below create a representation of the above scenario.

/// Event Types
public class SomethingFailedEvent
{
    public Exception Exception { get; set; }
}

public class FailedResponse
{
    public string ErrorMessage { get; set; }
}

/// SagaFixture Event Handler
public async Task Handle(SomethingFailedEvent message)
{
    var failedEvent = new FailedResponse
    {
        ErrorMessage = message.Exception.Message
    };

    await Bus.Publish(failedEvent);
}

Unit Test Code Snippet

[Test]
public void FailureEventReceivedCanPublishFailedMessageWithExceptionDetails()
{
    var bus = new FakeBus();
    var logger = new NullLogger<MockSaga>();
    var sagaFixture = SagaFixture.For(
        () => new MockSaga(logger, bus);

    var startSagaEvent = new MockSagaRequested();
    sagaFixture.Deliver(startSagaEvent);

    var argumentException = new ArgumentException("Invalid Argument");
    var somethingFailedEvent = new SomethingFailedEvent()
    {
        Exception = argumentException
    };

    sagaFixture.Deliver(failedEvent);

    var failedEventMessage = bus.Events.OfType<MessagePublished<FailedResponse>>().Single();
    failedEventMessage.EventMessage.ErrorMessage.Should().Contain("Invalid Argument");
}

Is it possible to support creating the FakeBus and setting the serialisation to be Newtonsoft.Json similarly to when configuring Rebus.

.Serialization(s => s.UseNewtonsoftJson(
    new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.Auto
    }))

Solution

  • It sounds like you'll simply have to configure the saga fixture's message serializer, which can be done like this:

    static RebusConfigurer GetConfigurer() => Configure
        .With(new BuiltinHandlerActivator())
        .Serialization(s => s.UseNewtonsoftJson());
    
    using var fixture = SagaFixture.For<SomeSaga>(GetConfigurer);
    
    fixture.Deliver(new SomeMessageWithLoop());
    

    You can see the test fixture for the feature here: https://github.com/rebus-org/Rebus.TestHelpers/blob/master/Rebus.TestHelpers.Tests/TestSagaFixture_CustomMessageSerializer.cs