使用 NserviceBus.Testing 当代码不是通过消息处理程序启动时,你如何测试发布的事件

Using NserviceBus.Testing how do you test the published event when the code is not initiated via a message handler

问题

如果当前正在测试的方法是通过传入消息(进入处理程序)以外的其他方法启动的,您如何测试事件的发布。 NServiceBus.Testing 的问题(据我所知)是它非常适合测试处理程序,这反过来导致 messages/events 成为 send/published。

背景:

我们有许多遗留系统,在不断努力转向适当的 SOA 实施的过程中,我们需要与一些遗留数据库作业集成。这些作业每 15 分钟对数据库执行一次操作,我们希望在某些情况发生时挂钩并发布事件。

我们有一个 windows 服务,它在 NsbHost 中 运行 但不包含任何处理程序。相反,我们使用 Quartz 创建一个每分钟运行一次的内部 cron 作业,轮询数据库以查找与指定模式匹配的记录,并在更新数据库之前发布一个事件,表明我们已经处理了该记录。这就是我们整合新旧系统的方式。

显然这不是一个理想的情况,但考虑到我们正处于 SOA 实施的过渡阶段,它和我们现在一样好。

详情及代码:

我们发布的事件界面是这样的

    public interface IPremiumAdjustmentFinalised
    {
        string PolicyNumber { get; set; }
        decimal Amount { get; set; }
        DateTime FinalisedOn { get; set; }
    }

实际发布活动的代码是

    Bus.Publish<IPremiumAdjustmentFinalised>(e =>
    {
        e.Amount = AdjustmentAmount;
        e.PolicyNumber = PolicyNumber;
        e.FinalisedOn = LastModifiedOn;
    });

一切正常,我们可以测试调用是使用最小起订量进行的:

    eventPublisher.MethodToTest();
    bus.Verify(x => x.Publish(It.IsAny<Action<IPremiumAdjustmentFinalised>>()), Times.Once);

其中 bus 是插入到构造函数中的新 Mock。

但我想测试 IPremiumAdjustmentFinalised 中的值。我发现这真的很难我认为主要是因为它是一个接口而不是具体的 class.

我们已尝试使用 NServiceBus.Testing 来尝试检查生成的事件,但无济于事。

有谁知道如何在此给定场景中测试事件中的值?

我们提出了 2 个解决方案

解决方案一:

创建一个具体的 class 来继承接口和总线发布

public class PremiumAdjustmentFinalisedEvent : IPremiumAdjustmentFinalised
{
    public string PolicyNumber { get; set; }
    public decimal Amount { get; set; }
    public DateTime FinalisedOn { get; set; }
}

这个class只在发送方使用,另一端的处理程序仍然会监听 IPremiumAdjustmentFinalised 类型的事件。

发送事件会变成这样

var message = new PremiumAdjustmentFinalisedEvent
{
    Amount = AdjustmentAmount,
    PolicyNumber = PolicyNumber,
    FinalisedOn = LastModifiedOn
};    
Bus.Publish<IPremiumAdjustmentFinalised>(message);

这让我们可以这样测试:

bus.Verify(x => x.Publish(It.Is<IPremiumAdjustmentFinalised>(paf => 
    paf.Amount == AdjustmentAmount && 
    paf.FinalisedOn == LastModifiedOn && 
    paf.PolicyNumber == PolicyNumber)), Times.Once);

这不是一个理想的解决方案,因为我们需要向实时解决方案添加代码以启用测试,但它运行良好且易于理解。

方案二:

实现我们自己的 IBus 并断言对象在其中是正确的:

 class myBus : IBus
 {
     public void Publish<T>(Action<T> messageConstructor)
     {
         IPremiumAdjustmentFinalised paf = new PremiumAdjustmentFinalised();
         var ipaf = (T) paf;
         messageConstructor(ipaf);

         Assert.AreEqual(paf.Amount, AdjustmentAmount);
         Assert.AreEqual(paf.FinalisedOn, LastModifiedOn);
         Assert.AreEqual(paf.PolicyNumber, PolicyNumber);
     }
     ...
     <<Leave the rest of the methods not implemented>>
     ...
 }

使用我自己的 IPremiumAdjustmentFinalised 测试实现(类似于解决方案 1,但在测试项目中)

现在测试看起来像这样

var myBus = new myBus();
eventPublisher = new AdjustmentFinaliserEventPublisher(myBus);
eventPublisher.UpdateAndPublishFinalisedAdjustments();

我喜欢这个解决方案,因为它不会仅仅为了测试而更改实时代码,但缺点是有一个很大的 IBus 实现,其中包含大量未实现的方法。

在找到这篇文章后搜索 google 结果几天后,除了这篇文章之外似乎仍然没有真正的指导。实施解决方案 2 后,我决心找到一种更轻松的方法,并使用 Moq 作为模拟库提出了以下方案。

///Declarations
private Mock<IPremiumAdjustmentFinalised > _mockEvent = new Mock<IPremiumAdjustmentFinalised >();
private readonly Mock<IBus> _mockBus = new Mock<IBus>();
private SomeEventPublisher _someEventPublisher;

[SetUp]
public void Setup()
{
     //Instruct moq to set up implementations for the event interface's properties.
    _mockEvent.SetupAllProperties();

    _mockBus.Setup(b => b.Publish(It.IsAny<Action<IPremiumAdjustmentFinalised>>()))
    .Callback((Action<IPremiumAdjustmentFinalised> messageConstructor) =>
    {
        //Execute the constructor provided in the action in the event publisher, so the event data can be interrogated.
        messageConstructor(_mockEvent.Object);
    });

    _someEventPublisher = new SomeEventPublisher(_mockBus);
}


[Test]
public void SomeEventPublisherTest()
{
    _someEventPublisher.PublishSomeEvent(AdjustmentAmount, LastModifiedOn, PolicyNumber)

    Assert.AreEqual(_mockEvent.Object.Amount, AdjustmentAmount);
    Assert.AreEqual(_mockEvent.Object.FinalisedOn, LastModifiedOn);
    Assert.AreEqual(_mockEvent.Object.PolicyNumber, PolicyNumber);
}