如何防止虚拟方法被模拟?

时间:2013-01-10 03:53:18

标签: c# unit-testing abstract-class rhino-mocks

我们有一个基类为INotifyPropertyChanged提供了一些默认实现(这个类被许多其他类使用,不能轻易更改):

public class ObservableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // this is virtual so derived classes can override it (rarely used, but it is used)
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

现在我有了一个接口和一个派生自ObservableBase的抽象基类,并实现了该接口,提供了一些默认实现(主要用于属性):

public interface INamedTrigger
{
    string Name { get; }
    void Execute();
}

public abstract class ObservableNamedTriggerBase : ObservableBase, INamedTrigger
{
    private string _Name;
    public string Name
    {
        get { return _Name; }
        set { _Name = value; OnPropertyChanged("Name"); }
    }

    public abstract void Execute();
}

现在我想对ObservableNamedTriggerBase的默认实现进行单元测试(使用NUnit和RhinoMocks):

[TestFixture]
public class ObservableNamedTriggerBaseTest
{
    [Test]
    public void TestNameChangeRaisesPropertyChanged()
    {
        var prop = "";
        var mocks = new MockRepository();
        var namedBase = mocks.PartialMock<ObservableNamedTriggerBase>();
        namedBase.PropertyChanged += (s, e) => prop = e.PropertyName;
        namedBase.Name = "Foo";
        Assert.AreEqual("Name", prop);
    }
}

不幸的是,此测试失败,因为Rhino似乎覆盖了OnPropertyChanged的虚拟ObservableBase方法。

SO questionRhino docs表示PartialMock应完全执行此操作。另一方面,这个SO question的答案表明,无论模拟的类型如何,Rhino都会覆盖虚方法。

文档错了吗?我该如何正确测试?

更新:如果我创建自己的模拟类,只为抽象方法提供虚拟实现,那么测试通过,但我想使用Rhino模拟来保存我正是这样做的工作。

4 个答案:

答案 0 :(得分:2)

不确定您使用的是哪个版本,但似乎您将RR( Record-Replay )语法用法与最近的AAA( Arrange-Act-Assert )。基本上,这样做:

var repo = new MockRepository();
var mock = repo.PartialMock<ObservableNamedTriggerBase>();

还要求你至少这样做:

repo.ReplayAll();

缺乏这种情况,会导致模拟行为不端(如果您设定期望/存根,则可能需要拨打repo.Record())。考虑到现代语法可用(starting with Rhino 3.5):

,这是不必要的努力
// no repository needed at all
var mock = MockRepository.GeneratePartialMock<ObservableNamedTriggerBase>();

MockRepository上的新静态方法正在处理引擎盖下的记录重放设置。

答案 1 :(得分:1)

一个想法是创建一个测试子类,该方法不可覆盖。

public abstract class TestingObservableNamedTriggerBase : ObservableNamedTriggerBase {
  protected override sealed void OnPropertyChanged(string propertyName) {
    base.OnPropertyChanged(propertyName);
  }
}

然后通过这个子类进行测试。

我还没有测试过这是否有效......

答案 2 :(得分:0)

您可以使用Rhinomocks的CallOriginalMethod方法调用原始方法。

namedBase.Expect(x => x.OnPropertyChanged(Arg<string>.Is.Anything))
         .CallOriginalMethod(OriginalCallOptions.NoExpectation);

如果测试中的类包含在测试程序集中的其他程序集中,则可能需要使用OnPropertyChanged方法protected internal并为测试程序集添加InternalVisibleToAttribute

同样如您所知,将InternalVisibleToAttribute("DynamicProxyGenAssembly2")添加到模拟内部类型。

答案 3 :(得分:0)

您可以使用CallBase布尔属性来告诉moq是使用原始虚方法还是使用默认值覆盖它们。正如它在财产摘要中所说:

  

CallBase摘要:如果未匹配任何设置,是否将为模拟类调用基本成员虚拟实现。默认为false。

所以使用这个:

mock.CallBase = true;