How can I prevent virtual methods from being mocke

2019-07-06 09:14发布

问题:

We have a base class providing some default implementation for INotifyPropertyChanged (this class is used by many other classes and cannot be easily changed):

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));
        }
    }
}

Now I have an interface and an abstract base class deriving from ObservableBase and implementing that interface providing some default implementations (mainly for the properties):

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();
}

Now I want to unit test the default implementations of ObservableNamedTriggerBase (using NUnit and 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);
    }
}

Unfortunately this test fails as Rhino seems to override the virtual OnPropertyChanged method from ObservableBase.

This SO question and the Rhino docs indicate that PartialMock should exactly not do this. On the other hand the answer to this SO question indicates that Rhino always overrides virtual methods regardless of the type of mock.

So is the documentation wrong? And how do I go about testing this correctly?

Update: If I create my own mock class providing dummy implementations for just the abstract methods then the test passes but I'd like to use Rhino mocks to save me the work of exactly doing that.

回答1:

Not sure which version are you using, but it seems that you're mixing RR (Record-Replay) syntax usage with more recent AAA (Arrange-Act-Assert). Basically, doing this:

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

Requires you also to at least do this:

repo.ReplayAll();

Lack of which, will cause mocks to misbehave (a call to repo.Record() might be needed if you're setting expectations/stubbing too). This is unnecessary effort considering modern syntax is available (starting with Rhino 3.5):

// no repository needed at all
var mock = MockRepository.GeneratePartialMock<ObservableNamedTriggerBase>();

New static methods on MockRepository are taking care of the record-replay setup under the hood.



回答2:

An idea is to create a testing subclass where the method is not overridable.

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

And then test it through this subclass.

I haven't tested if this works though...



回答3:

You can use the CallOriginalMethod method of Rhinomocks to call the original method.

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

If the class under the test is included other assembly from the test assembly, you may need to make the OnPropertyChanged method protected internal and to add InternalVisibleToAttribute for the test assembly.

Also as you already know, add InternalVisibleToAttribute("DynamicProxyGenAssembly2") to mock internal types.



回答4:

You can use CallBase boolean property to tell moq whether uses original virtual methods or override them with default values. As it said in its summary for the property:

CallBase Summary: Whether the base member virtual implementation will be called for mocked classes if no setup is matched. Defaults to false.

So use this:

mock.CallBase = true;