DEV Community

Cover image for How to test private methods in C#
Rahul Kumar Jha
Rahul Kumar Jha

Posted on

How to test private methods in C#

Introduction

A quick search online tells us that we are doing something wrong by testing private methods.

As a matter of fact we shouldn't be testing the private methods. Tests are for public interfaces.

The reasons and when we might need this is another discussion, let's concentrate on what we can do if we must.

Enough talk! Let's jump right into the code.

Implementation

Let's define a simple POCO class Student:

public class Student
{
     public string Name { get; set; }
     public string Email { get; set; }
     public bool IsActive { get; set; }
     public bool Notified { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Next, let's add a dummy class TestPrivateMethod that has a public method SendNotifications calling private methods NotifyStudents and SaveNotifyStatusToDb:

namespace TestPrivateMethod
{
    public class StudentNotifications
    {
        public bool SendNotifications(Student student)
        {
            var success = NotifyStudents(student);

            if (success)
            {
                var code = SaveNotifyStatusToDb(student);

                if (code <= 0) { return false; }
            }

            return success;
        }

        private bool NotifyStudents(Student student)
        {
            if (!student.IsActive || student.Notified)
            {
                return false;
            }

            // some code to simulate event call and get the status
            return true;
        }

        private int SaveNotifyStatusToDb(Student student)
        {
            // some code to simulate save to db
            return 1;
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

Since SendNotifications relies on both NotifyStudents and SaveNotifyStatusToDb: we can't test either private method.

Although we could refactor, we decided not to do that.

We still want to be able to test NotifyStudents independently.

Let's see what general options we have to test NotifyStudents.

  1. Why not just make NotifyStudents public
  2. Move NotifyStudents to a seperate class of its own

It makes no sense to make a private method public, so neither solution seems sensible.

Testing Private Methods using Reflection

We can invoke a private method utilizing MethodInfo:

  1. Create a Type object of the class StudentNotifications
  2. Use the Activator.CreateInstance to instantiate the type
  3. Get the private method info as MethodInfo using type.GetMethod
  4. Invoke the method passing the object[] parameter
  5. Verify the result

Let's write the test with all the steps using xUnit:

[Fact]
public void When_Active_Then_True()
{
    // get the type
    Type type = typeof(StudentNotifications);

    // create the object of the type
    var studentNotifications = Activator.CreateInstance(type);

    // get the private method
    MethodInfo method = type.GetMethod("NotifyStudents",
            BindingFlags.NonPublic | BindingFlags.Instance);

    // prepare parameters object accepted by the private method
    object[] parameters = { 
       new Student 
       { 
         Name ="TestA", 
         Notified=false, 
         IsActive=true 
       } 
    };

    // invoke the method
    bool result = (bool) method.Invoke(
       studentNotifications, 
       parameters);

    // verify the result
    Assert.True(result);
}
Enter fullscreen mode Exit fullscreen mode

The test runs!! The issue is though that the test might break:

type.GetMethod(
  "NotifyStudents",  // this is not strongly typed
   BindingFlags.NonPublic | 
   BindingFlags.Instance)
Enter fullscreen mode Exit fullscreen mode

What happens when someone renames the NotifyStudents? The change will not auto-updated in the test.

We would require to modify the test to make it work again.

Certainly, we do not adhere to the principle that unit tests should work always once written.

Next, let's have a look on another way of testing the private methods.

Testing Private Methods using Wrapper Function

Let's add a wrapper function NotifyStudentWrapperFunction in class StudentNotifications:

internal protected bool NotifyStudentWrapperFunction(Student student) 
{
    return NotifyStudents(student);
}
Enter fullscreen mode Exit fullscreen mode

NotifyStudentWrapperFunction simply calls the NotifyStudents passing the required parameter.

Doesn't it defeat the purpose of NotifyStudents being private in the first place?

The detail is in the NotifyStudentWrapperFunction accessibility - it is marked with internal protected.

The accessibility restriction makes this method inaccessible to outside world.

Now, let's check if we can test NotifyStudents using the NotifyStudentWrapperFunction

[Fact]
public void When_Active_Then_True_Using_Wrapper_Function()
{
    var sut = new StudentNotifications();

    var result = sut.NotifyStudentWrapperFunction(
     new Student 
     { 
        Name = "TestA", 
        Notified = false, 
        IsActive = true 
     });

    // verify the result
    Assert.True(result);
}
Enter fullscreen mode Exit fullscreen mode

We have compile error for sut.NotifyStudentWrapperFunction. That's a no brainer; we can't access internal protected members outside their own assembly.

Then how are we supposed to use the wrapper function?

There comes the .Net framework to our rescue - the framework provides us with a very useful attribute to help us with unit testing - InternalsVisibleTo.

[assembly: InternalsVisibleTo("TestPrivateMethod.Tests")]
namespace TestPrivateMethod
{
    public class StudentNotifications {....}
    internal protected bool NotifyStudentWrapperFunction(
    Student student) {....}

}
Enter fullscreen mode Exit fullscreen mode

We can mark the namespace with InternalsVisibleTo to make the internal protected method visible to the specified assembly - in this case the unit test assembly.

Now, we have no error at sut.NotifyStudentWrapperFunction(...). Test runs successfully.

Conclusion

We learned how we can test private methods - the pitfalls of using reflection for the purpose; a better alternative to it - using a wrapper function respecting the accessibility level of the method.

Leave a comment, share and tell us what other ways we could test the private methods.

Happy coding!!!

Top comments (1)

Collapse
 
john_parker_31103c38b8315 profile image
John Parker

Great article!!

Thanks :)