5

I have to write a unit test for an internal or private method in an abstract class in c#. consider this:

Public Abstract Class MyAbstractClass
{
 Private String MyMethod()
 {
   //Do Something
   return "HI";
 }
}

And, my test method:

public void MyTestMethod()
{
    var testProcessor = new Mock<MyAbstractClass>
    {
        CallBase = true
    };
    var privateType = new PrivateType(typeof(MyAbstractClass));
    PrivateObject privateObject = new PrivateObject(testProcessor , privateType);
    var resValue = (String)privateObject.Invoke("MyMethod");
    Assert.AreEqual(resValue ,"HI");
}

but when I run the test method, I'm getting below error:

 System.Reflection.TargetException: Object does not match target type.

How to resolve that?

Joy
  • 85
  • 6
  • 2
    Why do you need to test an abstract class. It's uninstantiable. You can't be using it directly in your code. Test the derived classes that your code used – Flydog57 Sep 04 '21 at 03:14
  • 1
    If you are using `Moq` you should likely pass the mocked instance and not the mocking wrapper... So: `PrivateObject privateObject = new PrivateObject(testProcessor.Object , privateType);` is perhaps what you want. – lidqy Sep 04 '21 at 06:59
  • By the way: Using a mocked instance of your abstract class you don't test that class. – lidqy Sep 04 '21 at 07:00

3 Answers3

4

You should not be testing private methods:

I do not unit test private methods. A private method is an implementation detail that should be hidden to the users of the class. Testing private methods breaks encapsulation.

See this post for more details.

As Flydog57 says, you should be testing a derived class (not the abstract one).

You can do this easily by creating a private test class inside your test class, and use this private class in your tests.

private class MyConcreteTestClass : MyAbstractClass 
{
     public string SomeMethodThatCallsMethodInAbstractClass()
     {
          ....
     }
}

public void MyTestMethod()
{
    // Arrange
    var testProcessor = new MyConcreteTestClass();

    // Act
    var result = testProcessor.SomeMethodThatCallsMethodInAbstractClass();

    // Asset
    Assert.AreEqual(result ,"HI");
}
openshac
  • 4,966
  • 5
  • 46
  • 77
  • Yeah it seems the right solution, but with Moq we don't need to do that. Moq handles everything. more discussion below (@lidqy) – Joy Sep 04 '21 at 17:10
2

Assuming you are using Moq as a mocking tool, the exception shown results of passing testProcessor instead of testProcessor.Object. If you change that line to...

PrivateObject privateObject = new PrivateObject(testProcessor.Object, privateType);

...you'll get rid of this error.
This is how it should work (in NET Framework, PrivateObject and such like has not been ported to NET Core MSTest):

//productive code
    public abstract class MyAbstractClass
    {
        private string MyMethod()
        {
            //Do Something
            return "HI";
        }
    }

    //testproject

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var testProcessor         = new Moq.Mock<MyAbstractClass>();
            var privateType           = new PrivateType(typeof(MyAbstractClass));
            var privateObject         = new PrivateObject(testProcessor.Object, privateType);
            var resValue              = (string)privateObject.Invoke("MyMethod");
            Assert.AreEqual(resValue, "HI");
        }
    }

By the way, I' didn't know a mock of an abstract class will execute the implementation of the abstract class. I thought it would create stubs that return defaults, as a mock of an interface does.
Interesting feature...

lidqy
  • 1,891
  • 1
  • 9
  • 11
  • 1
    Thanks lidqy! that was my solution. Yeah with Moq we don't need to create a derived class from abstract class. it handles everything. I just did not know that I need to use Object here. I tried it for typeof(MyAbstractClass) but it was not the right place. anter changing the privateObject, it was resolved! good catch! – Joy Sep 04 '21 at 17:07
0

You need to specify the BindingFlags to be NonPublic in order to find the private method.

I am not sure what is the PrivateObject but this should work (you may change it a little bit to make it suitable for your needs):

Type type = typeof(MyAbstractClass);

MethodInfo method = type.GetMethod(
    methodName, BindingFlags.NonPublic | BindingFlags.Instance);

string result = (string)method.Invoke("MyMethod", null);
Misha Zaslavsky
  • 8,414
  • 11
  • 70
  • 116