Testing an abstract base class – code example

In the “How do I test an abstract base class if I can’t instantiate it?” post, you found out that you can actually test an abstract base class even though you can’t instantiate one.

The “all you need to do is” end of the post, however, was a bit too abstract for your taste. It left you wondering how to put it all together. So let’s do it together.

Let’s say you have this nice little abstract class:

    abstract class Bashful
    {
        protected abstract string LetDescendantComposeFinalResult(string textToUseInComposition);
        protected abstract string LetDescendantProvideDefault();

        public string DoSomethingUseful(string textToUseInComposition)
        {
            // to implement
        }
    }

In your tests for DoSomethingUseful you want to ensure that Bashful actually defers composing the final result to its concrete descendants. You also want to ensure that Bashful always defers to its concrete descendants to get a default value should the passed in string be null or empty.

The tests themselves aren’t that difficult set up. If Bashful were a concrete class you would code your tests like this:


    [TestClass]
    public class Bashful_Tests
    {
        [TestMethod]
        public void DoSomethingUseful_WhenPassedNull_ShouldUseValueProvidedByDescendant()
        {
            Bashful = new Bashful();

            string testResult = Bashful.DoSomethingUseful(null);

            Assert.AreEqual("Composed:DescendantDefault", testResult);
        }

        [TestMethod]
        public void DoSomethingUseful_WhenPassedEmpty_ShouldUseValueProvidedByDescendant()
        {
            Bashful = new Bashful();

            string testResult = Bashful.DoSomethingUseful(string.Empty);

            Assert.AreEqual("Composed:DescendantDefault", testResult);
        }

        [TestMethod]
        public void DoSomethingUseful_WhenPassedExplicitValue_ShouldUsePassedInValue()
        {
            Bashful = new Bashful();

            string testResult = Bashful.DoSomethingUseful("ProvidedValue");

            Assert.AreEqual("Composed:ProvidedValue", testResult);
        }
    }

But of course this gets you in trouble because Bashful is an abstract class and you are not allowed to create instances of it.

So, as said in the “How do I test an abstract base class if I can’t instantiate it?” post, you need a concrete descendant specifically to test the base class.


    [TestClass]
    public class Bashful_Tests
    {
        private class Bashful_Tester : Bashful
        {
            protected override string LetDescendantComposeFinalResult(string textToUseInComposition)
            {
                return "Composed:" + textToUseInComposition;
            }

            protected override string LetDescendantProvideDefault()
            {
                return "DescendantDefault";
            }
        }
        // ...
    }

Very basic. But that is good. And it is enough. It allows you to see whether LetDescendantComposeFinalResult was used and what was passed into it. And that, after all, is the exact purpose for this concrete descendant of Bashful.

You could now replace

    Bashful = new Bashful();

with

    Bashful = new Bashful_Tester();

and you would be all set, because Bashful is so simple that it doesn’t require more to set it up for testing. But let’s say it were more complicated. Then you could create a helper method to instantiate the class you are testing and provide it with any dependencies or initialization as required by your tests. The idea is to do something like:


    [TestClass]
    public class Bashful_Tests
    {
        // ...
        private Bashful _Bashful;

        protected void ArrangeBashfulInstance()
        {
            _Bashful = new Bashful_Tester();
        }

The “Arrange” and “Act” parts of your tests will now look like:

    ArrangeBashfulInstance();

    string testResult = _Bashful.DoSomethingUseful(null);

Note the use of the private _Bashful instead of the local Bashful in the first incarnation of the test methods.

The Bashful class is pretty simple and doesn’t really require anything to set up. But supposing it were a bit more complicated and you wanted each of your tests to use specific test values. What then?

Well, this is where parameters on the “Arrange” method and “helper” methods on the concrete test descendant class come in.

Adding parameters on the ArrangeBashfulInstance method allows each test to set up the instance to meet its specific testing requirements. Adding extra “helper” methods to the concrete descendants is a way to allow your ArrangeBashfulInstance method to do much more than the public interface of the Bashful abstract base class allows.

For the sake of this discussion let’s say that your tests require that they each can control the default value returned by LetDescendantProvideDefault. So you change the Bashful_Tester class to:

    private class Bashful_Tester : Bashful
    {
        protected override string LetDescendantComposeFinalResult(string textToUseInComposition)
        {
           return "ComposedFinalResult:" + textToUseInComposition;
        }

        protected override string LetDescendantProvideDefault()
        {
            return UseAsDefault;
        }

        public string UseAsDefault { get; set; }
    }

That nice, but fairly useless as your tests don’t have access to UseAsDefault because the private _Bashful field is of type Bashful.

A way out is to change that private field to be of type Bashful_Tester.

And that would work.

But…

I recommend against it. For one, you would unnecessarily be duplicating code in each test to set the UseAsDefault value. For another it couples your tests way too tightly to the Bashful_Tester interface, when they should be focused on the Bashful class.

The way to have your cake and eat it too is to leave _Bashful declared as being of type Bashful, add a parameter to the ArrangeBashfulInstance method and change it to:

    protected void ArrangeBashfulInstance(string useAsDefault)
    {
        Bashful_Tester tester = new Bashful_Tester();
        tester.UseAsDefault = useAsDefault;
        _Bashful = tester;
    }

That’s it. Enjoy!

Please do feel free to let me know about any roadblocks you hit by email or in the comments below! I promise I read everything and will try to help where I can.

Posted in Unit Testing
Tags: , , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Show Buttons
Hide Buttons