Invoking the Static Constructor via Reflection
— Warning! —
This post may cause the reader a slight feeling of nausea. However, remember that tough situations call for some tough actions!
The Situation
Imagine that you have to write some-unit tests for a class which uses an existing infrastructure, involving calls to static methods on a static class. To make things worse, this static class also holds some state which, unfortunately, gets carried around between tests without no actual ability to clear it.
public static class MyInfrastructureClass { private static Dictionary_someState = new Dictionary (); private static List _someOtherState = new List (); public static void SomeOperation1() { // ... } public static void SomeOperation2() { // ... } }
After a unit-test is run, the state of the above class is not fresh, and as a result unit-tests can influence each-other and their success is dependant on the order in which they are run. This is obviously a very bad situation to be in.
Possible Solution
If there is no inherent built-in way to clear the state of the static class, then one can think of two possible solutions:
- Add a “Reset()”-like static method especially for unit-tests which is responsible of reinitializing the class’ state. While this solution is feasible, it involves writing special code for unit-tests which has to be maintained, and if the static class’ code is not yours this is not possible in the first place.
- Dynamically invoke the static constructor via reflection – this is the solution I would like to demonstrate.
Invoking the Static Constructor
Not many people know this, but it is possible to invoke the static constructor via reflection, much like the way you dynamically invoke any other method.
ConstructorInfo constructor = typeof (MyInfrastructureClass) .GetConstructor(BindingFlags.Static | BindingFlags.NonPublic, null, new Type[0], null); constructor.Invoke(null, null);
The above code can be placed at the initialization or teardown stage of each unit-test, resulting in a clean-up of the static class’ state. This looks nice, however when using this solution you should be very careful – dynamically invoking the static constructor breaks the CLR’s promise that the static constructor would be called exactly once. This might break up the class’ implementation if it does not expect it and should be thoroughly inspected. With the above being said and kept in mind, if the static constructor only initializes and clears up the class’ state this might be an easy solution for unit-testing when infrastructure code refactoring is not possible.
My Two Cents
Static classes are a possible .NET implementation for the Singleton Pattern. This implementation has it strengths, however in my opinion it should be avoided as much as possible. The reason for this is mainly Unit-Test unfriendliness – static classes encapsulate state which cannot be easily reinitialized and implementation which cannot be easily replaced with mocks using standard, open mocking libraries.
When required to implement the Singleton Pattern, I usually prefer using a singleton instance accessed via an interface. For example:
public class MySingletonClass : IMySingletonClass { private static readonly MySingletonClass _instance = new MySingletonClass(); public static IMySingletonClass Instance { get { return _instance; } } private MySingletonClass() { // Singleton initialization } // IMySingletonClass implementation // ... }
This way clearing the state between unit-tests is very easy (just replace the singleton instance used), and replacing the implementation with a mock is also relatively easy but requires a degree of cooperation for the tested client class which should allow injecting the instance used from the outside.
Conclusion
I wouldn’t usually recommend dynamically invoking the static constructor, however the technique is sometimes useful for unit-tests and should be kept in your toolbox. Just remember – use it with caution!
In addition, when possible, try to avoid using static classes. Singleton instances are, in my opinion, a better way to go.