Unit Testing Internal Methods

I’ll come straight out with it, I don’t like unit tests residing within the dll that they are testing, I know that some people quite like this approach and it could be argued that it allows the clients to verify that the unit tests are indeed in place and working correctly. For me though they are not a part of the production code and just add unnecessary bloat to it, this is allied to the fact that sometimes test rely on the infrastructure and there may also be a set amount of database interaction which for a live client is a big no no!

Instead I prefer all of my unit tests to be in a single or suite of test projects that are invoked sometime during the build process to verify the quality of the code. Recently we have been retroactively fitting some unit-testing to new functionality within a legacy project (which means that we do not of course have dependancy injection!) and I came up against an issue that we have come up against before but have always cludged our way around. The issue is one of scope, I like to test the validation routines for the business layer in isolation but we never ordinarily make our business layer validation methods public instead relying on the main ‘Update’ method to perform the validation prior to save. I like to call the validation methods in isolation because it means that the tests are more performant which in turn means you can be more thorough and reap the rewards of a more stable and tested application. Ideally I like to make the validation methods private or protected but for the sake of unit testing I will compromise and make them internal, this way they can be invoked from anywhere within the business layer but NOT from outside. OK, now scan back to my first paragraph and you can see where my problem lies… I don’t like unit tests in the dll itself.

Luckily .NET has a solution to this problem in that internal methods may be made visible to named external libraries; thus our unit testing library can invoke the required validation methods without exposing the same methods in the public API. However, all of the examples I have seen for implementing this are very poorly documented and so I thought as an aide memoire for myself, and as a service to the internet at large I would share my experiences.

For the purposes of this discussion I have three projects, the three projects must either ALL be signed or ALL unsigned, no if’s no buts. For the purposes of this discussion I will be using signed assemblies.

  1. MyProject.Business which is where the business rules reside – “C:\My Projects\MyProject.Business”
  2. MyProject.UI which is where the main application user interface resides -“C:\My Projects\MyProject.UI”
  3. MyProject.UnitTests which is where all of my Nunit tests reside – “C:\My Projects\MyProject.UnitTests”

MyProject.Business has the following class defined within it:-

namespace MyProject {
  public class ContactsBusiness {

    public void Update(ContactsDS data) {
       ....... Code...

       ValidateContacts(data);

       ....... More Code....
    }
    internal void ValidateContacts(ContactsDS data) {
       ....... Code...
    }
    private void SomeTopSecretMethod(ContactsDS data) {
       ....... Code...
    }
}
}

Now as things stand the only method visible to the external MyProject.UI and MyProject.UnitTests is the main Update method which will in turn invoke the ValidateContacts method. We now however wish to open this up so that the ValidateContacts method is also visible to the MyProject.UnitTests library but NOT MyProject.UI.

First things first, as I am using signed assemblies I will need the full public key for my business assembly, this can be obtained by opening the visual studio command line and typing the following command:-

sn -Tp "C:\My Projects\MyProject.Business\Bin\MyProject.Business.dll"

This returns the following public key for the assembly:-

00240000048000009400000006020000002400005253413
10004000001000100d3485b9bbad933c83462d897d9121bc93c09
1c396c6f080a53c8812c1855a170038014b21ab10152583a8fe08
c2ea2e5919af8078e97ada5c54f0f403dcb491da323623ad4340a
294862d10440b0ab25746eea9311f0f9b5e2dd7c849ca1135bb65
225131d959a10737c44639303c27e775771d7a4e523f32d14ae31
9041acc9

We now add the following InternalsVisibleTo attribute to the AssemblyInfo.cs fle  (located within the properties folder of the MyProject.Business projects) . If your assemblies are unsigned the public key is not required at all.

[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
[assembly: InternalsVisibleTo("MyProject.UnitTests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100d3485b9bbad933c83462d897d9121bc93c091c396c6f080a53c8812c1855a170038014b21ab10152583a8fe08c2ea2e5919af8078e97ada5c54f0f403dcb491da323623ad4340a294862d10440b0ab25746eea9311f0f9b5e2dd7c849ca1135bb65225131d959a10737c44639303c27e775771d7a4e523f32d14ae319041acc9")]

Note that we are giving the MyProject.UnitTests project (in its entirety) access to all methods and classes marked as internal residing within the MyProject.Business assembly. All private and protected methods are of course unaffected and behave in the same manner. With this in place I can now test the validation methods in isolation allowing us to increase and better target our unit testing meaning that our products can be more thoroughly tested before they even leave the development workbench.

Advertisements

One comment

  1. This is a great post in that testing internal methods is really helpful when you are working with legacy code.

    That being said, once you are adding tests and, I’m sure, refactoring you should probably extract the validation methods into their own class. That way you are complying with the Single Responsibility Principle and you do not have to do anything special to test internal methods since now they became public in their class.

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 )

Google+ photo

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

Connecting to %s