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

Dam*_*amo 3 .net c# unit-testing moq nservicebus

问题

如果当前正在测试的方法是通过传入消息(进入处理程序)之外的其他方法启动的,那么如何测试事件的发布。NServiceBus.Testing 的问题(据我所知)是它非常适合测试处理程序,从而导致消息/事件被发送/发布。

背景:

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

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

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

详细信息和代码:

我们正在发布的事件界面如下所示

    public interface IPremiumAdjustmentFinalised
    {
        string PolicyNumber { get; set; }
        decimal Amount { get; set; }
        DateTime FinalisedOn { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

实际发布事件的代码是

    Bus.Publish<IPremiumAdjustmentFinalised>(e =>
    {
        e.Amount = AdjustmentAmount;
        e.PolicyNumber = PolicyNumber;
        e.FinalisedOn = LastModifiedOn;
    });
Run Code Online (Sandbox Code Playgroud)

这一切都工作正常,我们可以测试使用最小起订量进行的调用,如下所示:

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

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

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

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

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

Dam*_*amo 6

我们提出了 2 个解决方案

解决方案一:

创建一个继承接口并总线发布的具体类

public class PremiumAdjustmentFinalisedEvent : IPremiumAdjustmentFinalised
{
    public string PolicyNumber { get; set; }
    public decimal Amount { get; set; }
    public DateTime FinalisedOn { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

此类仅在发送方中使用,另一端的处理程序仍会侦听 IPremiumAdjustmentFinalished 类型的事件。

发送事件将更改为这样

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

这使我们能够测试:

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

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

解决方案2:

实现我们自己的 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>>
     ...
 }
Run Code Online (Sandbox Code Playgroud)

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

现在测试看起来像这样

var myBus = new myBus();
eventPublisher = new AdjustmentFinaliserEventPublisher(myBus);
eventPublisher.UpdateAndPublishFinalisedAdjustments();
Run Code Online (Sandbox Code Playgroud)

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