问题
如果正在测试的当前方法是通过除传入消息之外的其他方法(进入处理程序)启动的,那么如何测试事件的发布。 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尝试检查生成的事件,但无济于事。
有谁知道如何在这个给定场景中测试事件中的值?
答案 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);
}