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

时间:2015-05-06 10:16:17

标签: c# .net 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; }
    }

实际发布活动的代码是

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

这一切都很好,我们可以测试使用MOQ进行的调用:

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

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

但我想测试IPremiumAdjustmentFinalised中的值。我觉得这很难找,主要是因为它是一个界面而不是一个具体的类。

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

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

2 个答案:

答案 0 :(得分:3)

我们提出了两种解决方案

解决方案1:

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

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

此类仅用于发送方,另一端的处理程序仍会侦听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);

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

解决方案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>>
     ...
 }

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

现在测试看起来像这样

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

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

答案 1 :(得分:1)

在找到这篇文章后几天搜索谷歌搜索结果后,似乎除了这个之外似乎没有真正的指导。在实施解决方案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);
}