Shar1er80
Shar1er80

Reputation: 9041

Unit Test protected method of an abstract class

I'm trying to write a unit test to test a protected method in an abstract class. I've tried writing a test class that inherits from the abstract class, but when I instantiate the test class the base abstract class attempts to connect to an Oracle database and fails which doesn't allow me to test the protected method I'm interested in. The abstract class cannot be modified.

How can I directly unit test a protected method in this abstract class?

Here is snippet of what I tried with reflection.

Type type = typeof(AbstractClass);
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.NonPublic;
MethodInfo myMethod = type.GetMethod("ProtectedMethod", eFlags);
object[] arguments = new object[] { _myDs };
myMethod.Invoke(type, arguments);
_myDs = (DataSet)arguments[0];

Upvotes: 1

Views: 6911

Answers (3)

Dariusz Woźniak
Dariusz Woźniak

Reputation: 10350

It is doable, but please note there may be couple of serious problems with your code:

  • Constructor shouldn't perform any business logic.
  • Your abstract class should not know about database type (Dependency Inversion Principle).
  • Is your abstract class have more responsibilities? If yes, move them to separate classes / projects (Single Responsibility Principle).
  • You shouldn't unit test non-public API.

Here's a recipe:

  • First, create a fake instance of your abstract class.
  • Then, call FormatterServices.GetUninitializedObject for getting instance of the class without calling constructor.
  • After that, you can use reflection for invoking ProtectedMethod.

By that, you are able to test ProtectedMethod:

  • Without calling constructor.
  • Without modifying abstract class.

And here is an example code:

class AbstractClassFake : AbstractClass { }

[Test]
public void Test()
{
    // Arrange:
    var abstractClassFake = (AbstractClassFake)FormatterServices
        .GetUninitializedObject(typeof(AbstractClassFake));

    MethodInfo method = abstractClassFake.GetType()
        .GetMethod("ProtectedMethod",
            BindingFlags.Instance | BindingFlags.NonPublic);

    object[] arguments = new object[] { myDs };

    // Act:
    object val = method.Invoke(abstractClassFake, new[] { myDs });

    // Assert:
    // TODO: Your assertion here
}

Upvotes: 3

tallseth
tallseth

Reputation: 3665

Compromise.

Say you have:

public abstract class CanNeverChange
{
    public CanNeverChange()
    {
        //ACK!  connect to a DB!!  Oh No!!
    }

    protected abstract void ThisVaries();

    //other stuff
}

public class WantToTestThis : CanNeverChange
{
    protected override void ThisVaries()
    {
         //do something you want to test
    }
}

Change it to this:

public class WantToTestThis : CanNeverChange
{
    protected override void ThisVaries()
    {
         new TestableClass().DoSomethingYouWantToTest();
    }
}

public class TestableClass
{
    public void DoSomethingTestable()
    {
        //do something you want to test here instead, where you can test it
    }
}

Now you can test the behavior you want to test in TestableClass. For the WantToTestThis class, compromise to the pressure of terrible legacy code, and don't test it. Plugging in a testable thing with a minimal amount of untested code is a time honored strategy; I first heard of it from Michael Feather's book Working Effectively with Legacy Code.

Upvotes: 1

Chris Missal
Chris Missal

Reputation: 6123

I think the best solution is to break the class apart as the comments suggest.

However, if you would like to test it without changing the class very much, you could move the contents of the constructor into a virtual method:

public abstract class TheOracleOne
{
    protected virtual void Init()
    {
        // connection things
    }
}

And override in your derived class to do nothing:

public class TestClass : TheOracleOne
{
    protected override void Init()
    {
    }
}

This should skip the initialization that is taking place in the abstract class's constructor, allowing you to access the method normally on the derived class.

Upvotes: 0

Related Questions