Image Image Image Image Image
Scroll to Top

To Top

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 | 14 Kommentare | 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! 😀

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.

  1. It is 100% noninvasive. (when running in release-mode the aspect won’t even exist)
  2. 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.)

Tags | ,

Kommentare

  1. Blogged: http://blog.johanneshoppe.de/2011/02/uni… – Unit Tests: The Rent Is Too Damn High – Faking System.DateTime.Now #aop #postsharp

    • oocx

      @JohannesHoppe Ich habe das so gelöst wie hier: http://bit.ly/e6JKwS Funktioniert und ist simpel.

      • @oocx Hmmm… Jo, das ist auch ein Wrapper. Sieht schick aus, mit der Lambda-Expression. Hätte { return.DateTime.Now } nicht auch gereicht?!

      • @oocx Ich stell mir die Frage, ob Mocks nicht ein Gebiet für AOP wären. Vielleicht sollte ich mich mal an HttpRuntime.Cache ranwagen…

  2. Blog Update: http://blog.johanneshoppe.de/2011/02/uni… – Unit Tests: The Rent Is Too Damn High – Faking System.DateTime.Now #aop #postsharp @oocx

    • oocx

      @JohannesHoppe Deine Alternative sieht aber auch interessant aus, und ich nutze seit kurzem ja sowieso PostSharp.

    • oocx

      @JohannesHoppe Mal schaun ob man damit auch andere Sachen testen könnte, z.B. Datei Ein-/Ausgabe.

      • @oocx @cessor Prinzipiell geht alles. Die Frage ist nur, ob man um PostSharp.Sdk „herum kommt“. Habt ihr ein paar Anwendungsfälle?

    • brittrking

      RT @JohannesHoppe: Blog Update: http://blog.johanneshoppe.de/2011/02/uni… – Unit Tests: The Rent Is Too Damn High – Faking System.DateTime.Now #aop #postshar …

    • RT @JohannesHoppe: Blog Update: http://blog.johanneshoppe.de/2011/02/uni… – Unit Tests: The Rent Is Too Damn High – Faking System.DateTime.Now #aop #postshar …

  3. cessor

    A Nice example. I prefer the Lambda Version, as it is very lightweight. Be careful. When everything you have is a hammer, everything else will start to look like a nail.

  4. For sure. And now I ask myself the question: are aspects a suitable nail for Unit Tests?! :-)

  5. Hi Johannes,

    Nice blog. There’re a lot of things you can do when you’re able to intercept any method. Personally, I would rather use a MethodInterceptionAspect than an OnMethodBoundaryAspect, but that’s not the point here.

    The technology behind the PostSharp AOP Framework is MSIL rewriting. You can use MSIL rewriting technology to achieve a mocking framework, however the AOP abstractions are not ideal for mocking. It does not mean that you can’t do that, but it will look odd and unnatural. So you would have to create a mocking framework with the right abstractions.

    So I think telling „use AOP for mocking“ is confusing. Now, if you write a fully-fonctional mocking framework based on build-time AOP — I mean a mocking framework that smells like a mocking framework — then this can be interesting.

    You’ll probably that creating such a framework is not a trivial thing and this would raise the rent too. But feel free to try :).

  6. I think I have found a new hobby horse! :-)

Kommentar absenden


Projekt-
Verfügbarkeit:
ab 01.01.2017