Implement Interface as virtual methods

A best pratice in Object Oriented Programming is the use of Interfaces. When applied correctly it simplifies unit testing. But watch out for the pitfall of default implementation by Visual Studio. Below is an example how this can bite you.

A Document can be printed using the IPrintable interface. The implementation of the interface calls the more specific print methods based on the paper size.

public interface IPrintable
{
    void Print();
    string GetPaperSize();
}
public class Document : IPrintable
{
    public void Print()
    {
        if (GetPaperSize() == "A4") PrintOnA4();
        else PrintOnDefault();
    }
    // implementation ignored for demo purpose
    public string GetPaperSize() { return string.Empty; }
    public virtual void PrintOnA4() { }
    public virtual void PrintOnDefault() { }
}

Now the unit test of the Print method. The test below wants to validate PrintOnA4 is called when the PaperSize is A4 and the Print method of IPrintable is called on the Document. For my unit tests I use Rhino mocks.

[TestMethod]
public void Document_Print_Papersize_A4_calls_PrintOnA4()
{
    // Arrange
    // PartialMock to call origional methods when no expectation is set up
    var testObject = MockRepository.GeneratePartialMock<Document>();
    // Return PaperSize "A4"
    testObject.Expect(x => x.GetPaperSize()).Return("A4");
    // Expect PrintOnA4 to be called
    testObject.Expect(x => x.PrintOnA4());
    // Expect PrintOnDefault *NOT* to be called
    testObject.Expect(x => x.PrintOnDefault()).Repeat.Never();

    // Act
    testObject.Print();

    // Assert
    testObject.VerifyAllExpectations();
}

Running the test results in an InvalidOperationException because GetPaperSize is not overridable on Document.
InvalidOperationException when mocking non-virtual method
It is part of the IPrintable interface and can be mocked there. This can be done by adding a second parameter to the GeneratePartialMock call. One mock is generated for both Document and IPrintable combined.

var testObject = MockRepository.GeneratePartialMock<Document, IPrintable>();
((IPrintable)testObject).Expect(x => x.GetPaperSize()).Return("A4");

Now the test fails because the PrintOnDefault method is called and the PrintOnA4 is not. What happened? We mocked the GetPaperSize to return “A4”.
The method is mocked on IPrintable, but not on Document. To use the IPrintable method we need to cast to it.

public void Print()
{
    if (((IPrintable)this).GetPaperSize() == "A4") PrintOnA4();
    else PrintOnDefault();
}

Now the test Passes. But do I want to cast every time I use something from an Interface?
The solution is to implement the interface with virtual methods. That way I can mock the methods directly on the class and don’t have to cast every time. Why is this not the default in Visual Studio?
Implement interface in Visual Studio

public interface IPrintable
{
    void Print();
    string GetPaperSize();
}
public class Document : IPrintable
{
    public virtual void Print()
    {
        if (GetPaperSize() == "A4") PrintOnA4();
        else PrintOnDefault();
    }
    // implementation ignored for demo purpose
    public virtual string GetPaperSize() { return string.Empty; }
    public virtual void PrintOnA4() { }
    public virtual void PrintOnDefault() { }
}

The first unit test in this post can be used to verify that PrintOnA4 is called and not PrintOnDefault when GetPaperSize returns “A4”.

About erictummers

My work as a recruited developer changes almost every month. I like challenges and sharing the solutions with others. On my blog I’ll mostly post about my work, but expect an occasional home project, productivity tip and tooling review.
This entry was posted in Test and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s