Quantcast
Channel: .NET – Marius Bancila's Blog
Viewing all articles
Browse latest Browse all 39

Unit testing non-public types and members for .NET projects

$
0
0

Unit testing is usually used for testing public types and members. However, there are cases when you might need to test types or members that are not public. These could be internal classes or private helper methods, for instance. Whether that is proper unit testing or beyond its scope is not a discussion that I want to get into here. However, in this post, I will show how you can unit test non-public types and members from .NET assemblies.

When faced with the need for testing non-public types and members you can use several approaches:

  • change the accessibility to public; you can do that perhaps only for debug builds and keep the intended accessibility in release builds by using conditional compilation.
  • provide public members of a class that invoke private ones;
  • use reflection.

The first solutions involve changing the API only for the sake of the testing. The last solution avoids that but requires more work. To help with that, the Visual Studio unit testing framework provides some helper types that enable you to focus on the actual testing and be less concerned about the reflection details.

Unit-testing internals

Let us assume we have the following internal class in a project called demolib:

namespace demolib
{
   internal class InternalClass
   {
      public int Run() { return 42; }

      internal int Add(int a, int b) { return a + b; }
   }
}

Both the InternalClass class and its Add method are internal. Therefore they are not accessible from another assembly, so the following code cannot work:

[TestClass]
public class UnitTests
{
   [TestMethod]
   public void TestInternalClass()
   {
      var obj = new InternalClass(); // 'InternalClass' is inaccessible due to its protection level

      var r1 = obj.Run();            // 'InternalClass.Run()' is inaccessible due to its protection level
      Assert.AreEqual(42, r1);

      var result = obj.Add(1, 2);    // 'InternalClass.Add(int, int)' is inaccessible due to its protection level
      Assert.AreEqual(3, result);
   }
}

This can be fixed, however, with a single line of code, added to the assembly that is being tested (not the testing assembly). You can put it into the AssemblyInfo.cs file:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("demotest")]

And with that, the above test can be compiled and executed successfully.

However, things are a bit different if the assembly that is being tested is a strong-named signed assembly. In this case the following error is triggered on the InternalsVisibleTo attribute:

Friend assembly reference ‘demotest’ is invalid. Strong-name signed assemblies must specify a public key in their InternalsVisibleTo declarations.

To solve this, you must:

  • sign the testing assembly too, and
  • specify the public key from the file that was used to sign the testing assembly.

To fetch the public key, you must run the following commands (this examples assume the key file used to sign the testing assembly is called demokey.snk):

sn -p demokey.snk publickey.snk
sn -tp publickey.snk

The result is as follows:

Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Public key (hash algorithm: sha1):
0024000004800000940000000602000000240000525341310004000001000100291fb5c9c86bda
4d78d59f9f8abe09ab5d9370dc77ebac20d03d6d4fdbf6c54cdaab8df6c4e467fa29ae00969835
c660936b2539bfec4c4da8bb0ba418feeb8e19d20166482c473ee8a0acbba288867f7a66740284
6266f8f9808ef85c68d0ee30c710e64e822ad77122da5fa47a6dcdd0ef1f3e9d9cddd18b74fefd
619074ec

Public key token is 4eda0eecea5894a0

With this information, you must change the InternalsVisibleTo declaration as follows:

[assembly:InternalsVisibleTo("demotest, PublicKey="+
"0024000004800000940000000602000000240000525341310004000001000100291fb5c9c86bda" +
"4d78d59f9f8abe09ab5d9370dc77ebac20d03d6d4fdbf6c54cdaab8df6c4e467fa29ae00969835" +
"c660936b2539bfec4c4da8bb0ba418feeb8e19d20166482c473ee8a0acbba288867f7a66740284" +
"6266f8f9808ef85c68d0ee30c710e64e822ad77122da5fa47a6dcdd0ef1f3e9d9cddd18b74fefd" +
"619074ec")]

Unit-testing privates

Let’s change the InternalClass above so that instead of being internal it is public but its members are protected and private. The solution shown earlier no longer works and we are back to the same error with members not being accessible due to their protection level.

namespace demolib
{
   class PublicClass
   {
      protected int Run() { return 42; }

      private int Add(int a, int b) { return a + b; }
   }
}

The Visual Studio unit testing framework provides a class called PrivateObject that helps invoking non-public non-static members through reflection.

The unit testing of the PublicClass using the PrivateObject helper would look as follows:

[TestMethod]
public void TestPublicClass()
{
   var obj = new PrivateObject(typeof(PublicClass));
   var r1 = (int)obj.Invoke("Run");
   Assert.AreEqual(42, r1);

   var r2 = (int)obj.Invoke("Add", 1, 2);
   Assert.AreEqual(3, r2);
}

However, PrivateObject cannot be used for invoking static members. Another helper, called PrivateType, is provided and must be used for this purpose.

Suppose the PublicClass type should change as shown below:

namespace demolib
{
   class PublicClass
   {
      protected int Run() { return 42; }

      private static int Add(int a, int b) { return a + b; }
   }
}

In this case, testing the Add() method must be done as follows:

[TestMethod]
public void TestPublicClass()
{
   var obj = new PrivateType(typeof(PublicClass));

   var r2 = (int)obj.InvokeStatic("Add", 1, 2);
   Assert.AreEqual(3, r2);
}

Additional readings

For more information see the following articles:


Viewing all articles
Browse latest Browse all 39

Trending Articles