How to mock DateTime.Now in unit tests

by toni 5. March 2012 12:52

The normal solution is to hide it behind interface.

public class RecordService
{
    private readonly ISystemTime systemTime;
    
    public RecordService(ISystemTime systemTime)
    {
        this.systemTime = systemTime;
    }
    
    public void RouteRecord(Record record)
    {
        if (record.Created < 
            systemTime.CurrentTime().AddMonths(-2))
        {
            // process old record
        }
        
        // process the record
    }
}

In the unit test you can use mock object and decide what to return

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    {
        var systemTime = A.Fake<ISystemTime>();        
        A.CallTo( () => system.Time.CurrentTime())
          .Returns(DateTime.Now.AddYears(-1));
        
        var record = new Record(DateTime.Now);
        var service = new RecordService(systemTime);
        
        service.RouteRecord(record);
        
        // Asserts...
    }
}

I don’t like to inject another interface into my class just to get the current time. It feels too heavy solution for a such a small problem. The solution is to use static class with public function. This solution is nothing new and you can find same example from way back 2008 from Ayende's blog.

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

Now we can remove the ISystemTime injection and RecordService looks like this

public class RecordService
{
    public void RouteRecord(Record record)
    {
        if (record.Created < SystemTime.Now.AddMonths(-2))
        {
            // process old record
        }
        
        // process the record
    }
}

In the unit tests we can mock the system time just as easily.

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    {
        SystemTime.Now = () => DateTime.Now.AddYears(-1);        
        var record = new Record(DateTime.Now);
        var service = new RecordService();
        
        service.RouteRecord(record);
        
        // Asserts...
    }
}

Of course there is a downside to all this. You are using public fields (The HORROR!) so nobody is stopping you writing code like this.

public class RecordService
{
    public void RouteRecord(Record record)
    {
        SystemTime.Now = () => DateTime.Now.AddYears(10);
    }
}

But why would you do something like that? Not even junior developer is likely to make such a mistake. Also I think it is better to educate developers than create abstractions just to protect them from doing any mistakes. Other possible issues are related to running the tests. If you forget to restore the function back to it's original state it might affect other tests. This depends on the way unit test runner executes the tests.

You can use the same logic to mock file system operations

public static class FileSystem
{
    public static Action<string, string> MoveFile = File.Move;
}

In my opinion implementing this kind of functionality (mocking time, simple file system operations) using public functions is perfectly acceptable. It makes the code easier to read, decreases the dependencies and it is easy to mock in the unit tests.

Comments (6) -

Chris Surfleet
Chris Surfleet United Kingdom
3/7/2012 4:43:26 PM #

Hmm, on one hand this does feel a bit hacky, but on the other its the best implementation I've seen so far!

Chris McKenzie
Chris McKenzie United States
3/8/2012 2:16:04 AM #

There is also the Clock implementation:
bitbucket.org/.../Clock.cs

Claudio Ribeiro
Claudio Ribeiro Brazil
3/8/2012 3:54:56 PM #

I am glad to catch idea from your article. It has information I have been searching for a long time. Thanks so much.

Claudio Ribeiro
Claudio Ribeiro Brazil
3/8/2012 3:56:09 PM #

I am glad to catch idea from your article. It has information I have been searching for a long time. Thanks so much.

Claudio Ribeiro
Claudio Ribeiro Brazil
3/8/2012 3:58:17 PM #

I am glad to catch idea from your article. It has information I have been searching for a long time. Thanks so much.

dissertation help
dissertation help United Kingdom
4/2/2012 6:23:19 PM #

Really great post nice work i love your work and its really helped me in my research.Thanks for sharingSmile

Dissertation Help
Dissertation Help United Kingdom
4/14/2012 9:17:35 AM #

That's look great that people would love to share their educational matters and experience on internet, this would be good for readers who must face again this issues in their projects.

sophie
sophie United States
4/21/2012 4:50:07 PM #

nice post

Gabe
Gabe United States
5/14/2012 5:56:55 PM #

Not bad. Smile But maybe it would be even better, if you use internal properties / functions, and use the friend assemblies (msdn.microsoft.com/.../0tke9fxk(v=vs.100).aspx) - so then you could avoid using public ones.

Oshawa Homes For Sale
Oshawa Homes For Sale United States
8/20/2012 4:30:57 PM #

I went over this web site and I believe you've a great deal of excellent information.

Pingbacks and trackbacks (1)+

Comments are closed