Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.
Oh, the Pain!
For a long time, unit testing non-public methods in C# has been a pain in the back side. In order to test a non-public method (most likely private methods) you have a number of choices each with an undesired effect:
- The easiest way out is to make the methods public. However, this breaks all the encapsulations in your application and can be potentially unsafe because in most cases these methods were not exposed publicly for a reason, e.g. a method which can leave your application in a bad state if used incorrectly.
- If making the methods public is a step too far, you can make these methods protected instead and have the unit test class extend this class to gain access to the protected methods. Still, this breaks the encapsulation in your application somewhat, and having a unit test class extend a business logic class ‘smells’. And once again, it can be unsafe as others can also extend your class to gain unintended access to these methods, the same reason why you would often want to seal the LAST type in an inheritance hierarchy – to mark and protect the boundaries of your application.
- The last and the most advanced technique is to use reflection to invoke these methods. The upside is that you won’t have to break the encapsulation of your application just to make them testable, the downside is that you pay for this in maintainability and complexity. What it means is that your test class now needs to have intimate knowledge of the class it is testing and because methods names are spelled out in string literals, renaming the non-public method becomes a more costly operation because you need to change all the reflection code used to invoke the method too. Personally this is my least favourite approach as the frequency of change and the number of unit tests in any sizable project can make the task of maintaining them rather unenviable (especially if it’s your job to do so!).
There’s a better way
Since .Net 2.0, you have another alternative thanks to the introduction of the InternalsVisibleTo attribute. It gives you the ability to expose methods marked internal to a specific assembly.
All you need to do is add one line of code to the AssemblyInfo.cs file of the assembly (say, MyApplicatoin) you want to test to allow your unit test assembly (let’s call it MyApplication.Test) to see all the types/methods that have been marked with the internal access modifier:
[assembly: InternalsVisibleTo("MyApplication.Test")]
You will also need to change the access modifier on any methods (in MyApplication) you wish to test to internal for you to be able to see it in MyApplication.Test.
Doing so, however, doesn’t stop you from sealing your type (point 2 above) and doesn’t break all the encapsulation of your application (methods are still inaccessible from external assemblies besides the unit test assembly) although it still breaks the encapsulation of your type because any private methods you wish to test have to be made internal instead and therefore accessible from outside the type.
This is still not ideal, but compared to the alternatives, is still the one that I’m most willing to live with.
Parting thoughts…
Before you jump in with both feet and start testing all the private/internal methods, there’s a couple of questions you should always ask yourself first:
- The most important question of them all is whether or not these methods are even worth testing!
- If they are worth testing, could they be moved into a separate class?
- Could you cover all the test cases by making more calls to the public methods that call the private methods?
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.