3

I have the following class(es) that I want to write unit tests for:

public abstract class BaseClass
{
    public bool IsFooBar(Order order)
    {
        return order.IsNew && IsFooBarOverride(order);
    }

    protected abstract IsFooBarOverride(Order order);
}

public class SimpleDerivedClass : BaseClass
{
    protected override bool IsFooBarOverride(Order order)
    {
        // Return if the order is with a credit card.
    }
}

public class ComplexDerivedClass : BaseClass
{
    protected override bool IsFooBarOverride(Order order)
    {
        // Return a very complex statement
        // If the order is for a new customer
        // in New York, with an order amount of $500 or more
        // and has at opted for email subscriptions.
    }
}

The problem I am running into is that my derived classes contain the majority of my logic for determining the return value of IsFooBar(Order order). I want to test this logic to ensure it is proper. I was under the impression that you should only test the public API of a class, which is IsFooBar(Order order), which belongs to the base class.

  • Should I redesign my class structure by marking IsFooBar virtual and hope that other developers on the team use base.IsFooBar(order) as part of their return statement?

  • Should I leave my class structure in tact and test variations of the IsFooBar(Order order) method per derived class as a way to test the logic in the protected class?

5
  • Possible duplicate of Should I unit test my subclasses or my abstract parent class?
    – gnat
    Nov 9, 2015 at 19:26
  • 4
    I think I agree with that question being a duplicate, but the answers there feel lacking to me so I'll hold off on my dupe vote on the off chance this one gets better answers (imo it's more focused/concise).
    – Ixrec
    Nov 9, 2015 at 19:31
  • The second one. I'm struggling to see why you wouldn't do this.
    – David Arno
    Nov 9, 2015 at 20:57
  • 2
    Unit tests should exercise against a public API, but test behaviors. To test a behavior, give the object a particular input and verify that the property you're expecting is upheld in the output, or in the tested object itself. Nov 10, 2015 at 15:33
  • @michael You say you want to test the subclass-specific variations, but you also want to test the invariant defined in BaseClass, right ? Nov 12, 2015 at 12:23

3 Answers 3

1

Considering your doubts, I would argue that the choice of solving this with inheritance might be the wrong approach.

Perhaps composition would be more appropriate in this case? It is hard to tell without knowing the requirements.

To answer the question, as long as the template method is the way to go, test per derived class.

1

Depending on your unit test framework, you can write an abstract base test class that will contain assertions about what your abstract production class should do.

All subclasses of that base test class will automatically run these "parent" tests as well as any other tests related to their specific concrete System Under Test.

To generate the System Under Test, you can resort to a Factory method (or Factory Property) pattern.

Example with NUnit :

[TestFixture]
public abstract class BaseTest
{
    protected abstract BaseClass Sut { get; }

    [Test]
    public void An_old__order_should_never_be_FooBar()
    {
        var result = Sut.IsFooBar(new Order {IsNew = false});
        Assert.IsFalse(result, "An old Order should never be FooBar no matter the concrete implementation");
    }
}

public class SimpleDerivedClassTest : BaseTest
{
    protected override BaseClass Sut { get { return new SimpleDerivedClass(); } }

    // Your other, SimpleDerived-specific tests here
}

public class ComplexDerivedClassTest : BaseTest
{
    protected override BaseClass Sut { get { return new ComplexDerivedClass(); } }

    // Your other, ComplexDerived-specific tests here
}

As a side note, in the particular example you chose it might not seem so easy to determine what assertions will hold in all circumstances and should therefore go in the base test class. But it is in fact quite trivial (all !IsNew Orders should be !FooBar...)

6
  • That's a lot of boilerplate just to accomodate a test framework. Nov 11, 2015 at 7:52
  • As opposed to... ? You make "accomodating a test framework" sound like it's the goal. It isn't. The goal is to verify that a property holds for all classes deriving from the base class. Nov 12, 2015 at 12:11
  • MSTest has had the ability to test private and protected members for years, with no accomodations required (other than to generate a private accessor object, but that's part of the test framework, not the code under test). Nov 12, 2015 at 15:43
  • What does this have to do with the question at hand ? What other solution do you propose that can test both the invariant from the base class elegantly (i.e. not by duplicating the same test code in each derivative's test) and verify the variations in behavior for these derivatives ? Nov 12, 2015 at 15:54
  • Your answer starts with the phrase "you can write an abstract base test class that will contain assertions..." That is not entirely about adding testing infrastructure to an already working program?" Nov 12, 2015 at 15:55
0

I was under the impression that you should only test the public API of a class, which is IsFooBar(Order order), which belongs to the base class.

Not quite.

That method is protected and so is only available to subclasses.
OK, it's accessible outside the class (to a degree), but it's certainly not "public".

... my derived classes contain the majority of my logic for determining the return value of IsFooBar(Order order). I want to test this logic to ensure it is proper.

Since the implementation of these overridden methods is what you need to test, then write tests for the concrete classes (I, for one, don't know how to test an instance method on an abstract class, because you can't instantiate that class so, from a purely practical standpoint, you have to work with (i.e. test) the subclasses.

If the bulk of the code were in [abstract] base class implementation of a given method, then you might consider writing a [concrete] subclass purely for testing purposes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.