Testing Internal Classes in .Net – Pragmatically
While working on a large .Net API, I ran into an interesting issue. The codebase was exposing functions in classes that were not necessary for the public API. Originally, this was done to facilitate testing. Several of the classes had methods involving complex calculations. It was easiest to test these in isolation. The code had top level functions that would call several other calculation functions. The result was a testable cluttered API.
While this was not an issue when we used our own API, it was a point of confusion for third parties using our API. There are two lines of thinking one could take.
Test Centric With this approach you justify your design by saying that it is more important to write the code to be testable than it is to worry about hiding a few methods.
Design Centric In this line of thinking you must put the importance of the design beyond that of tests. Testing is important, but it should not drive the design. For example, this approach shuns adding public properties to exam the internals of a class solely for the purpose of testing.
I decided to take a design centric approach. Modifying many access modifiers to internal, I soon had the API cleaned up. Using intellisense in Visual Studio showed a concise list of functions and methods. Utilizing our API would be straightforward and simple.
At this point I realized I needed to address all my failing unit tests. Now that the access modifiers were set to internal, the test harness was unable to see the methods being tested.
The code could have been refactored to pull out a small calculation engine. This then could then act as an internal member of the higher-level classes that comprised the API surface area. This would achieve the goal of making the code testable, while at least keeping the primary API classes clean. However, API users would still see the additional calculation class. The only true way to keep the users out of the lower-level code is to mark it as internal.
As it turns out there is a pragmatic solution to the issue.
In the project AssemblyInfo file add an attribute to expose the internal classes to your testing assembly.
Here is an example with VB attributes...
Exposing the assemblies to Test Framework, and to Moq's Proxy Generating Assembly
<Assembly: InternalsVisibleTo("Company.Library.Calculations.Test")> <Assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")>
Notice that I also expose to DynamicProxyGenAssembly2, this is so Moq's proxy engine can still create mocks for internal classes as well.
This works well. It’s a minimal pain, and allows us to hand our third party implementers a very clean API.