Tuesday, 12 September 2017
Richard Blewett
6 minute read
When using the decorator pattern it is a common practice to create a base class implementation of the abstraction that simply forwards all calls to the decorated class.
This means that in the actual functional decorators, once you derive from this base class, you only need to implement the behaviour for the specific methods of interest rather than every method. The caveat though is that, because C# methods are not virtual by default you must remember to mark all methods as virtual.
I used to think it gave little benefit to write unit tests for these base decorators but a couple of experiences have shown that without too much effort you can write tests that live as guards as the decorated abstraction evolves. The two main issues I have seen are:
To solve issue 1 we just make sure we have tests that cover each of the methods on the abstraction. As an example, take an abstraction for a prioritised queue:
public interface IPrioritizedQueue<T>
{
void Enqueue(T item, int priority);
T Depueue();
}
Now we can write tests for a decorator base class quite easily to verify forwarding:
[Test]
[TestCase(23)]
[TestCase{0)]
[TestCase(-1)]
pubic void Dequeue_WhenCalled_ShouldForwardCallToDecoratedAndReturnSame(int item)
{
var sut = CreateSut();
decorated.Setup(d => d.Dequeue())
.Returns(item)
.Verifiable();
int dequeuedItem = sut.Dequeue();
decorated.Verify();
Assert.That(dequeuedItem, Is.EqualTo(item));
}
However, how do we write a test to ensure all members are virtual?
Well, reflection to the rescue. We can look at each method in the SUT and verify it is virtual. However, beware that the MethodBase
class has an IsVirtual
method but this does not mean the same as the keyword in C#. Interface implementations also return IsVirtual
as true as they are also subject to virtual dispatch. What we really are looking for is a way to ask "is this method overridable?". To ensure that we also need to check that the IsFinal
flag on MethodBase
is false.
[Test]
public void Type_WhenExamined_ShouldMarkAllMethodsAsVirtual()
{
Type t = typeof(PrioritizedQueueDecorator<int>);
IEnumerable<MethodBase> methods = t.GetMethods()
.Where(m => m.DeclaringType == t);
Assert.That(methods.All(m => m.IsVirtual && !m.IsFinal));
}
Now we are much more likely to catch issues with the base class as the abstraction evolves.
Last updated: Monday, 12 June 2023
Director
He/him
Richard is a Director at Rock Solid Knowledge.
We're proud to be a Certified B Corporation, meeting the highest standards of social and environmental impact.
+44 333 939 8119