AOP Aspect-oriented programming (AOP) is a pragmatic passion that I document here.
Unit Tests – The Rent Is Too Damn High
On 06, Feb 2011 | inAOP | vonJohannes Hoppe
Faking System.DateTime.Now
For sure: The rent is too damn high!
And if we are writing Unit Tests, we are often paying a damn high price, too! Let’s look at a inconspicuous property that every .NET programmer is using:
System.DateTime.Now
Getting the current time is really easy. But how can we mock that statement for our tests?!
Solution 1
The first solution would be the usage of our favorite dependency injection framework. We would write a wrapper for DateTime.Now and produce an interface like this:
public interface IDateTime { DateTime Now { get; } }
Now we are ready to add a useless wrapper into our DI container. We do it for the sake of the unit test! Design for testability, Test Driven Development, AGILE – your manager will be happy!
Buuuuut this feels wrong: Inversion of control helps us to produce loosely coupled code. And as a great side-effect, we produce a lot of black-boxes that we can unit-test.
My top priorities for writing software are the KISS (Keep it simple, Stupid!)- and DRY (Don’t repeat yourself) principles. I think that code should be simple and stupid. The wrapper is adding a lot of unnecessary complexity to our software. And to be honest, how often do we change the concern „Time“ in our software?! Rarely, I would say. The wrapper and the interface are totally ignoring the YAGNI- (You ain’t gonna need it) principle, too. We should implement a time-switching-feature when we really need it!
Conclusion: Design (just) for testability? Nope!
The rent is too damn high! :-(
Solution 2
I really adore the Typemock Isolator. With Isolator you can test any .Net code. Isolator can change behavior of static methods, sealed classes, and private methods. Isolator is famous for being able to „fake the un-fake-able„. Hacking into DateTime.Now is damn sexy:
var newDate = new DateTime(2011,02,06); Isolate.WhenCalled(() => DateTime.Now).WillReturn(newDate);
That’s the way it should be. Isolator is a great product. But look at the prices page!
Conclusion: The rent is too damn, damn, damn high! ;-)
Solution 3
Let’s try something new. The possibility to change the time is just a concern that is required when we are going to unit-test our code. It doesn’t add any business value…
Looks like we have identified a cross-cutting-concern!
Cross-cutting-concerns are a jargon-word in the AOP (aspect oriented programming) world!
So let’s intercept System.DateTime.Now with an aspect. All we need is the free Community Edition of PostSharp. Yes, it’s free: You can build commercial products using the Community Edition and redistribute them.
namespace PostsharpAspects { using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; using PostSharp.Aspects; public static class NewDateTime { private static DateTime newDateTime = DateTime.MinValue; public static DateTime DateTime { get { return newDateTime; } set { newDateTime = value; } } } /// <summary>Will override System.DateTime.Now</summary> [Serializable] public class DateTimeNowOverrideAspect : OnMethodBoundaryAspect { public override void OnExit(MethodExecutionArgs args) { args.ReturnValue = NewDateTime.DateTime != DateTime.MinValue ? NewDateTime.DateTime : DateTime.Now; } public override bool CompileTimeValidate(MethodBase method) { return method.Name == "get_Now"; } } }
This aspect is manipulating every property that has the name „Now“. It is going to return the current time, or the time that is stored in the static class NewDateTime.Now.
Now we just need to apply this aspect to System.DateTime.
This line of code is sufficient:
#if DEBUG [assembly: PostsharpAspects.DateTimeNowOverrideAspect( AttributeTargetAssemblies = "mscorlib", AttributeTargetTypes = "System.DateTime")] #endif
It should be added to the assembly where the call should be intercepted. (In other words: it will have no effect when added to the Unit Test assembly!)
Et voilà! This work like charm! Our code stays clean and it is still fully unit-testable.
Conclusion: The rent is damn small now! :-D
Demo
I’m currently working on a demo solution for a lot of more aspects.
Go to http://webnoteaop.codeplex.com/ and check out the latest version.
The DateTimeNowOverrideAspect and a working UnitTest are included!
Discussion
Do you agree with my opinion? Should we design for testability?
Please leave a comment!
Update 2011-02-06:
Mathias Raacke (aka oocx) mentioned an elegant wrapper-solution.
By using a Func<T> delegate, the amount of effected code is shrinked to a simple replacement of DateTime.Now:
public static class SystemTime { public static Func<DateTime> Now = () => DateTime.Now; }
SystemTime.Now will always return DateTime.Now when called.
But in an unit test we can change the delegate like this:
SystemTime.Now = () => new DateTime(2008,1,1);
The technique is described here.
Conclusion: The rent is ok! ;-)
I still like the aspect-driven way.
- It is 100% noninvasive. (when running in release-mode the aspect won’t even exist)
- It works with legacy code.
Thanks for the feedback!
What do you think about the idea of Unit Testing with aspects?!
(Maybe I should fake/mock/stub HttpRuntine.Cache, which should be a better example.)