Unit testing non-public members


I often run into cases where I would like to have a unit test in place to test a class method, or even an entire class, that has been declared private or internal. But the project which holds the unit tests cannot see these classes and methods unless they are public.

That’s a problem. I don’t want to expose all my internal and private classes and methods as public just so that I can write unit tests for them. It means that users are now free to poke around a class and change the state in ways that I would never want them to be able to do. Even for my own ‘no-one-but-me-will-ever-see-this’ projects it is a pain since it means that IntelliSense displays will be cluttered with excess class members that I would rather were not listed.

So, what to do?

There is a way to expose your class members to your unit tests, while keeping them hidden from the outside world using the InternalsVisibleTo attribute. It does mean that you will have to change any private classes and/or members that you wish to test to internal. But that still allows them to be inaccessible to outsiders.

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("UnitTestingProjectName")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

Add the using reference and the attribute lines above the namespace declaration for any class that you don’t want exposed as public. Replace UnitTestingProjectName in the first line with the name of your specific unit testing project. This will allow your unit testing project to be able to access the class’ internal members.

The second line is specifically for use with Moq. It allows you create mock versions of the exposed internal members. If you are not using Moq, then just use the first line.

Another advantage of using the InternalsVisibleTo attribute to allow unit testing is that it allows you to reduce the clutter in your constructors caused by the need for dependency injection. As an example, look at the constructor for this class that takes a filename and does some work with the contents:

using System;
using System.IO;

namespace example
{
    public class IntegrityChecker
    {
        public IntegrityChecker()
        {
            /* do some initialization here */
        }


        public bool CheckFile(string filepath)
        {
            var fileIsGood = false;
            var contents = File.ReadAllText(filepath);
            Console.WriteLine($"checking {filepath}");

            /* check integrity and set fileIsGood here */
            
            return fileIsGood;
        }
    }
}

In order to build a unit test for the CheckFile() method, I might have to change it to look like this

using System;
using System.IO;

namespace example
{
    public class IntegrityChecker
    {
        private IUserDisplay _display;
        private IFileSystem _fs;

        public IntegrityChecker(IUserDisplay display = null, IFileSystem filesystem = null)
        {
            var _display = display ?? new SystemConsoleFacade();
            var _fs = filesystem ?? new WindowsFileSystem();
            /* do some initialization here */
        }

        public bool CheckFile(string filepath, )
        {
            var fileIsGood = false;
            var contents = _fs.File.ReadAllText(filepath);
            _display.WriteLine($"checking {filepath}");
          
            /* check integrity and set fileIsGood here */
            
            return fileIsGood;
        }
    }
}

just so that I can inject mocks for System.Console and System.File. Not only has this changed my constructor, but, like I said above, it is just inviting others to play around with the two new parameters.

Instead, I would now do the following:

using System;
using System.IO;

[assembly: InternalsVisibleTo("IntegrityCheckerUnitTesting")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

namespace example
{
    public class IntegrityChecker
    {
        private IUserDisplay _display;
        private IFileSystem _fs;

        public IntegrityChecker()
        {
            /* do some initialization here */
        }

        internal IUserDisplay Display 
        {
            get { return _display ??= new SystemConsoleFacade(); } 
            set { _display = value; } 
        }

        internal IFileSystem FS
        {
            get { return _fs ??= new WindowsFileSystem(); }
            set { _fs = value; }
        }

        public bool CheckFile(string filepath, )
        {
            var fileIsGood = false;
            var contents = FS.File.ReadAllText(filepath);
            Display.WriteLine($"checking {filepath}");

            /* check integrity and set fileIsGood here */

            return fileIsGood;
        }
    }
}
 

Now my constructor is back to its simpler, cleaner format, I can still inject mocks during my unit testing, and the Display and FS properties, which are only for internal use, remain hidden to outside users of my code.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s