犀牛模拟,接口和属性

时间:2009-08-28 17:05:59

标签: c# mocking rhino-mocks

我有一个类,它有一个我需要存根的属性。我不能将它作为构造函数的一部分传递,因为构造它的对象不知道构造函数的参数。

运行单元测试时,我希望能够将属性创建为存根。

这是我尝试过的,但它不起作用:

private DeviceMediator deviceMediator;
private IDeviceControlForm deviceControlForm;
private IDataAccess data;
private ICallMonitor callMonitor;

// Use TestInitialize to run code before running each test 
[TestInitialize()]
public void MyTestInitialize()
{
    // This line works fine
    deviceControlForm = MockRepository.GenerateStub<IDeviceControlForm>();          
    // This line works fine
    data = MockRepository.GenerateStub<IDataAccess>();
    // This has to be an ICallMonitor.  If I try to make it a 
    // CallMonitor then it fails.
    callMonitor = (CallMonitor)
      MockRepository.GenerateStub<ICallMonitor>();
    // This line does not compile.  Because it wants to 
    // return a CallMonitor not an ICallMonitor.
    Expect.Call(new CallMonitor(null)).Return(callMonitor);

    // This is the class that has the CallMonitor (called callMonitor).
    deviceMediator = new DeviceMediator(deviceControlForm, data);
}

无论如何都要捕获构造函数对CallMonitor的调用并使其实际成为存根?

如果它是相关的,这是DeviceMediator中的相关代码:

 private IDeviceControlForm form;
 private readonly IDataAccess data;
 public ICallMonitor CallMonitor { get; set; }

 public DeviceMediator(IDeviceControlForm form, IDataAccess data)
 {
     this.form = form;
     this.data = data;
     CallMonitor = new CallMonitor(OnIncomingCall);
 }

提前感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

首先,你可以直接在RhinoMock中存根/模拟类,所以如果你想要一个实际的CallMonitor存根而不是ICallMonitor,这将克服代码中的转换问题。转换失败的原因是RhinoMock创建了一个不是CallMonitor的“动态代理”对象。

其次你不能模拟构造函数调用,最重要的是没有办法在DeviceMediator构造函数中模拟对新CallMonitor的调用,因为没有办法注入实例。

通常的做法是将DeviceMediator构造函数更改为:

public DeviceMediator(IDeviceControlForm form, IDataAccess data, ICallMonitor callMonitor) { ... }

然后您的测试可以将此接口的存根/模拟实例注入构造函数。

编辑:如果你真的无法将一个实例注入构造函数,那么你有几个选择:

创建一个可以存根的工厂:

public class CallMonitorFactory
{
    public virtual CallMonitor CreateMonitor(args...) {  }
}

public DeviceMediator(IDeviceControlForm form, IDataAccess data, CallMonitorFactory factory)
{
    this.form = form;
    this.data = data;
    CallMonitor = factory.CreateMonitor(OnIncomingCall);
}

在DeviceMediator上添加受保护的工厂方法,该方法返回CallMonitor。然后,您必须在测试中手动创建DeviceMediator的子类,以便返回模拟CallMonitor对象。

将CallMonitor的构造函数参数移动到DeviceMediator构造函数中调用的方法/属性中。

您似乎正在尝试在CallMonitor上侦听某种事件,因此您可以(并且如果是这种情况)添加DeviceMediator订阅的事件。在这种情况下,您可以使用RhinoMock模拟事件引发调用,如下所示:

[Test]
public void IncomingCallTest()
{
    IEventRaiser callEvent;
    CallMonitor monitor = mocks.Stub(args..);
    using(mocks.Record())
    {
        callEvent = monitor.Stub(m => m.IncomingCall += null).IgnoreArguments().GetEventRaiser();
        //rest of expectations...
    }

    using(mocks.Playback())
    {
        DeviceMediator mediator = new DeviceMediator(form, data, monitor);
        callEvent.Raise(sender, args);
    }
}

但是,如上所述,你不能使用RhinoMock模拟构造函数调用,因为这需要对生成的IL进行一些更改(假设它甚至可能)。

答案 1 :(得分:1)

由于CallMonitor属性是可写的,您只需使用模拟实例覆盖原始值(您的DeviceMediator实际上实现了属性注入设计模式)。

所以你可以这样写一个测试:

[TestMethod]
public void MyTest()
{
    var deviceControlForm = MockRepository.GenerateStub<IDeviceControlForm>();
    var data = MockRepository.GenerateStub<IDataAccess>();
    var mockCallMonitor = MockRepository.GenerateStub<ICallMonitor>();

    var deviceMediator = new DeviceMediator(deviceControlForm, data);
    deviceMediator.CallMonitor = mockCallMonitor;

    // The rest of the test...
}

答案 2 :(得分:0)

我对Rhino没有太多经验,但你是否尝试在调用Return时将callMonitor转换为CallMonitor?

例如:

Expect.Call(new CallMonitor(null)).Return((CallMonitor)callMonitor);

编辑: 再想一想,看起来Return可能是一种通用方法,这意味着这可能是一个额外的选择

Expect.Call(new CallMonitor(null)).Return<CallMonitor>(callMonitor);