Home >

Writing Testable Time Dependent Code

2. December 2008

I’ve recently received an email about a code snippet I’ve provided in Ayende’s post. Ayende, as usual, provided a simple yet good code for this problem. Mine is more complex way of doing this, but i like the pattern behind it.

You should be aware that there is no way to make DateTime.Now stop(if you don’t want to touch system clock and run your test in virtual environment, of course). You have to abstract it.

public class TimeAbstractionTests
{
    public TimeAbstractionTests()
    {
        CallContext.SetData(Constants.CONTEXT_STORE_KEY,null);
    }


    [Fact]
    public void Time_should_set_to_System_time_for_first_time()
    {
        DateTime x = Time.Now;
        Assert.Equal(DateTime.Now, Time.Now);
    }

    [Fact]
    public void Time_set_to_system_time_when_only_system_time_is_selected()
    {
        using(Clock.System())
        {
            Assert.Equal(DateTime.Now, Time.Now);
        }
    }

    [Fact]
    public void Time_should_freeze_when_frozen_time_is_selected()
    {
        DateTime frozenTime=new DateTime(2011,11,11);
        using (Clock.Frozen(frozenTime))
        {
            Assert.Equal(frozenTime, Time.Now);
        }
    }

    [Fact]
    public void Time_should_nest()
    {
        DateTime frozenTime = new DateTime(2011, 11, 11);
        using (Clock.Frozen(frozenTime))
        {
            Assert.Equal(frozenTime, Time.Now);
            using (Clock.System())
            {
                Assert.Equal(DateTime.Now, Time.Now);
                using (Clock.Frozen(frozenTime))
                {
                    Assert.Equal(frozenTime, Time.Now);
                }
            }
        }
    }
}

This tells what I’d like to achieve pretty well. I want one FrozenTime, one System time, and I want them to nest(no specific reason actually)

For nesting, Stack<T> structure should be used. Everytime I ask for a Clock, it puts the demanded Time into stack stored in CallContext, and Time.Now thing takes the latest ITime instance from that stack.

Implementation for FrozenTime and DefaultTime is simple, here they are.

public class FrozenTime:ITime
{
    public FrozenTime(DateTime now)
    {
        this.now = now;
    }

    private readonly DateTime now;
    #region ITime Members

    public DateTime Now
    {
        get { return now; }
    }

    #endregion
}public class DefaultTime:ITime
{
    #region ITime Members

    public DateTime Now
    {
        get { return DateTime.Now; }
    }

    #endregion
}

Now it comes to the Time class. As I said, it gets the topmost ITime(using Peek(), since we don’t want to remove) instance from the stack and invokes .Now on it.

public static class Time
{

    public static DateTime Now
    {
        get
        {
            Stack<ITime> timeStack = 
(Stack<ITime>) CallContext.GetData(Constants.CONTEXT_STORE_KEY); if (timeStack == null||timeStack.Count==0) { Clock.System(); timeStack =
(Stack<ITime>) CallContext.GetData(Constants.CONTEXT_STORE_KEY); } return timeStack.Peek().Now; } } }

Clock class is where I say that I want xxxxTime to be active during the scope. In order to support using(Clock.System()) like usage, I must have something IDisposable. I would have implemented IDisposable for ITime implementations, but it violates SRP so I choose another way.

public class Clock
{
    public static IDisposable System()
    {
        var time = new DefaultTime();
        PushIntoStack(time);
        return new DisposeCallback(PopFromStack);
    }
   . . . . . . 
}

As you see, when somebody calls .Dispose() in callback(which is done at the end of using() structure) it pops the latest ITime instance.

This is definitely more work than Ayende’s solution, but I like this way more.

You can download the solution here(Time.rar (35.61 kb))

kick it on DotNetKicks.com

,

Comments

12/2/2008 7:48:46 PM #
Trackback from DotNetKicks.com

Writing Testable Time Dependent Code
1/26/2009 8:38:07 AM #
How about making FrozenTime inherit from IDisposable so you can using it?
1/26/2009 8:40:36 AM #
Could be but i have to put some dispose logic in it, which i don't like to.
1/26/2009 8:41:02 AM #
BTW, I found that the file is missing, i hope i can find it in my computer.
3/22/2011 4:46:30 AM #
A person like you deserves an applause! Being one of the virtual assistants, I hope I could also make a good site like this as it relates to my recent job.        
3/23/2011 5:17:05 AM #
Thumbs up! I agree with all the things you posted on your site. Unfortunately i don't have enough to time to share my insights about this one. I need to get back to my virtual assistants work. I'll find time next time! See yah!
3/23/2011 9:22:57 AM #
I am not sure where you're getting your information, but great topic. I needs to spend some time learning more or understanding more. Thanks for fantastic info I was looking for this information for my mission.
Comments are closed