Thursday, November 15, 2012

Testing private methods with Visual Studio

When writing unit tests in most of the cases we have to test private methods. Let's see how this can be achieved using Visual Studio. I will give you three options:

  1. You can test private methods using Reflection, but this is not always a good option, as when you change the name or input arguments of a Method, you won't get any error when building, but you will get exceptions run time.
    We will use the project from my previous post Getting console output within a unit test.
    We will add a private method to the DummyClass:
    using System;
    
    namespace ConsoleLogger
    {
        public class DummyClass
        {
            public void WriteToConsole(string text)
            {
                Console.Write(text);
            }
    
            private void PrivateWriteToConsole(string text)
            {
                Console.Write("Private: " + text);
            }
        }
    }
    
    Now we can add a unit test to test the method using reflection:
    [TestMethod]
    public void PrivateWriteToConsoleReflection()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass target = new DummyClass();
        Type type = typeof(DummyClass);
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            var method = type.GetMethod("PrivateWriteToConsole", 
                BindingFlags.NonPublic | BindingFlags.Instance);
            method.Invoke(target, new object[1] { text });
    
            Assert.AreEqual(string.Format("Private: {0}", text), 
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    We get and invoke the method using reflection.

  2. Another options is to use PrivateObject class. Using it you can easily call private method, but you have the same problem, you won't get compile exception when name or parameters are changed. Here is the same test written using PrivateObject:
    [TestMethod]
    public void PrivateWriteToConsolePrivateObject()
    {
        var currentConsoleOut = Console.Out;
    
        PrivateObject target = new PrivateObject(typeof(DummyClass));
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.Invoke("PrivateWriteToConsole", text);
    
            Assert.AreEqual(string.Format("Private: {0}", text), 
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
  3. And here is the third option which I think is the best. You can add .accessor file containing the name of the assembly whose private methods you want to see, or you can use Visual Studio "Create Unit Tests..." wizard to do this for you:

    this will add the following unit test:
    /// 
    ///A test for PrivateWriteToConsole
    ///
    [TestMethod]
    [DeploymentItem("ConsoleLogger.exe")]
    public void PrivateWriteToConsoleTest()
    {
        DummyClass_Accessor target = new DummyClass_Accessor(); // TODO: Initialize to an appropriate value
        string text = string.Empty; // TODO: Initialize to an appropriate value
        target.PrivateWriteToConsole(text);
        Assert.Inconclusive("A method that does not return a value cannot be verified.");
    }
    
    we will modify it for our needs:
    /// 
    ///A test for PrivateWriteToConsole
    ///
    [TestMethod]
    [DeploymentItem("ConsoleLogger.exe")]
    public void PrivateWriteToConsoleTest()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass_Accessor target = new DummyClass_Accessor();
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.PrivateWriteToConsole(text);
    
            Assert.AreEqual(string.Format("Private: {0}", text),
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
    When Visual Studio builds the project it will generate ConsoleLogger_Accessor.exe assembly, containing DummyClass_Accessor class with public methods only.
  4. Just to mention that you can test internal methods using the same approaches, but you will have one more option - in the AssemblyInfo file of the assembly being tested, you can add  InternalsVisibleTo attribute to specify which assembly will see the internal methods, in our case:
    [assembly: InternalsVisibleTo("ConsoleLogger.Tests")]
    
    now we will add an internal method to the same class:
    using System;
    
    namespace ConsoleLogger
    {
        public class DummyClass
        {
            public void WriteToConsole(string text)
            {
                Console.Write(text);
            }
    
            private void PrivateWriteToConsole(string text)
            {
                Console.Write("Private: " + text);
            }
    
            internal void InternalWriteToConsole(string text)
            {
                Console.Write("Internal: " + text);
            }
        }
    }
    
    and here is the working test method:
    [TestMethod]
    public void InternalWriteToConsoleTest()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass target = new DummyClass();
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.InternalWriteToConsole(text);
    
            Assert.AreEqual(string.Format("Internal: {0}", text),
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
And here's the code - ConsoleLogger.zip

No comments:

Post a Comment