What's a good way to overwrite DateTime.Now during testing?
Asked Answered
I

13

129

I've got some (C#) code that relies on today's date to correctly calculate things in the future. If I use today's date in the testing, I have to repeat the calculation in the test, which doesn't feel right. What's the best way to set the date to a known value within the test so that I can test that the result is a known value?

Internalize answered 4/9, 2008 at 13:2 Comment(0)
S
167

My preference is to have classes that use time actually rely on an interface, such as

interface IClock
{
    DateTime Now { get; } 
}

With a concrete implementation

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

Then if you want, you can provide any other kind of clock you want for testing, such as

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

There may be some overhead in providing the clock to the class that relies on it, but that could be handled by any number of dependency injection solutions (using an Inversion of Control container, plain old constructor/setter injection, or even a Static Gateway Pattern).

Other mechanisms of delivering an object or method that provides desired times also work, but I think the key thing is to avoid resetting the system clock, as that's just going to introduce pain on other levels.

Also, using DateTime.Now and including it in your calculations doesn't just not feel right - it robs you of the ability to test particular times, for example if you discover a bug that only happens near a midnight boundary, or on Tuesdays. Using the current time won't allow you to test those scenarios. Or at least not whenever you want.

Safety answered 4/9, 2008 at 13:6 Comment(5)
We actually formalized this in one of the xUnit.net extensions. We have a Clock class that you use as a static rather than DateTime, and you can "freeze" and "thaw" the clock, including to specific dates. See is.gd/3xds and is.gd/3xduConfidential
It's also worth noting that when you want to substitute your system clock method - this will happen, for example, when using a global clock in an enterprise with branches in widely scattered time zones - this method gives you valuable business-level freedom to change the meaning of "Now".Rhapsody
This way works really well for me, along with using a Dependency Injection Framework to get to the IClock instance.Undercroft
Great answer. Just wanted to add that in almost all cases UtcNow should be used and then adjusted appropriately according to the concern of the code, e.g business logic, UI, etc. DateTime manipulation across timezones is a minefield but the best first foot forward is to always start with UTC time.Placer
@BradWilson Those links are now broken. Couldn't pull them up on WayBack, either.Star
H
58

Ayende Rahien uses a static method that is rather simple...

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
Haye answered 4/9, 2008 at 13:14 Comment(3)
It seems dangerous to make the stub/mock point a public global variable (class static variable). Wouldn't it be better to scope it to just the system under test -- eg, making it a private static member of the class under test?Breaking
It's a matter of style. This is the least thing you can do to get a unit-test-changeable system time.Haye
IMHO, it's preferred to use interface instead of global static singletons. Consider the following scenario: The test runner is efficient and running as many tests as he can is parallel, one test changes to given time to X, the other to Y. We now have a conflict and these two tests will toggle failures. If we use interfaces, each test mocks the interface according to his needs, each test is now isolated from other test. HTH.Prolamine
S
25

Using Microsoft Fakes to create a shim is a really easy way to do this. Suppose I had the following class:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

In Visual Studio 2012 you can add a Fakes assembly to your test project by right clicking on the assembly you want to create Fakes/Shims for and selecting "Add Fakes Assembly"

Adding Fakes Assembly

Finally, Here is what the test class would look like:

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}
Salvo answered 3/1, 2014 at 14:56 Comment(2)
This was exactly what I was looking for. Thanks! BTW, works the same in VS 2013.Appropriate
Or these days, VS 2015 Enterprise edition. Which is a shame, for such a best practice.Alidaalidade
P
20

I think creating a separate clock class for something simple like getting the current date is a bit overkill.

You can pass today's date as a parameter so you can input a different date in the test. This has the added benefit of making your code more flexible.

Proofread answered 4/9, 2008 at 13:4 Comment(2)
I +1'd on both yours and Blair's answers, even though they are both opposing. I think both approaches are valid. Your approach I would probably use a project that doesn't use something like Unity.Amagasaki
Yes, it is possible to add a parameter for "now". However, in some cases this requires you to expose a parameter that you would normally not want to expose. Let's say, for example, that you have a method that calculates the days between a date and now, then you do not want to expose "now" as a parameter because this allows manipulation of your result. If it's your own code, go ahead add "now" as parameter, but if you are working in a team you never know what other developers will use your code for, so if "now" is a critical part of your code you need to protect it from manipulation or misuse.Batten
I
12

The key to successful unit testing is decoupling. You have to separate your interesting code from its external dependencies, so it can be tested in isolation. (Luckily, Test-Driven Development produces decoupled code.)

In this case, your external is the current DateTime.

My advice here is to extract the logic that deals with the DateTime to a new method or class or whatever makes sense in your case, and pass the DateTime in. Now, your unit test can pass an arbitrary DateTime in, to produce predictable results.

Indictment answered 9/9, 2008 at 14:53 Comment(0)
M
11

Another one using Microsoft Moles (Isolation framework for .NET).

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

Moles allows to replace any .NET method with a delegate. Moles supports static or non-virtual methods. Moles relies on the profiler from Pex.

Misplay answered 2/2, 2010 at 12:9 Comment(5)
This is beautiful, but it requires Visual Studio 2010! :-(Risible
It works fine in VS 2008 as well. It works best with MSTest though. You can use NUnit, but then I think you have to run your tests with a special test runner.Tophole
I would avoid using Moles (a.k.a. Microsoft Fakes) when possible. Ideally, it should only be used for legacy code that is not already testable via dependency injection.Snell
@brianpeiris, what are the downsides for using Microsoft Fakes?Mandibular
You can't always DI 3rd party content, so Fakes is perfectly acceptable to unit test if your code calls into a 3rd party API that you don't want to instantiate for a unit test. I agree to avoid using Fakes/Moles for your own code, but its perfectly acceptable for other purposes.Hysterogenic
U
5

I'd suggest using IDisposable pattern:

[Test] 
public void CreateName_AddsCurrentTimeAtEnd() 
{
    using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
    {
        string name = new ReportNameService().CreateName(...);
        Assert.AreEqual("name 2010-12-31 23:59:00", name);
    } 
}

In detail described here: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

Unsuccess answered 2/2, 2010 at 11:52 Comment(0)
A
4

Simple answer: ditch System.DateTime :) Instead, use NodaTime and it's testing library: NodaTime.Testing.

Further reading:

Ameliorate answered 18/11, 2014 at 12:23 Comment(0)
R
3

You could inject the class (better: method/delegate) you use for DateTime.Now in the class being tested. Have DateTime.Now be a default value and only set it in testing to a dummy method that returns a constant value.

EDIT: What Blair Conrad said (he has some code to look at). Except, I tend to prefer delegates for this, as they don't clutter up your class hierarchy with stuff like IClock...

Rousseau answered 4/9, 2008 at 13:6 Comment(0)
C
1

I faced this situation so often, that I created simple nuget which exposes Now property through interface.

public interface IDateTimeTools
{
    DateTime Now { get; }
}

Implementation is of course very straightforward

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

So after adding nuget to my project I can use it in the unit tests

enter image description here

You can install module right from the GUI Nuget Package Manager or by using the command:

Install-Package -Id DateTimePT -ProjectName Project

And the code for the Nuget is here.

The example of usage with the Autofac can be found here.

Crosscrosslet answered 27/6, 2018 at 13:43 Comment(0)
W
1

.NET 8 introduces an alternative approach. You can read more about it here.

Moreover, the Microsoft.Extensions.TimeProvider.Testing exposing the FakeTimeProvider class that can be used in tests.

Here is an example:

public class ServiceClass
{
    private readonly TimeProvider _timeProvider;

    public TimeExperiments(TimeProvider timeProvider)
    {
        this._timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
    }

    public void ServiceMethod()
    {
        long timestamp = this._timeProvider.GetTimestamp();
        DateTimeOffset localTime = this._timeProvider.GetLocalNow();
        DateTimeOffset utcTime = this._timeProvider.GetUtcNow();
        
        // Later you can use time-dependent data throughout the `this._timeProvider` instance.
    }
}

And this is how to use the FakeTimeProvider:

public void TestServiceMethod()
{
    // This creates a clock whose time is set to midnight January 1st 2000.
    var fakeTimeProvider = new FakeTimeProvider();
    var service = new ServiceClass(fakeTimePRovider);
    
    // Later you can use this service.
}

Note: This is still an experimental feature as .NET 8 is still in preview.

Wimmer answered 22/6, 2023 at 13:21 Comment(0)
R
0

Just pass DateTime.Now as a parameter to the method that needs it. Can't be more simple than that. https://blog.thecodewhisperer.com/permalink/beyond-mock-objects

Rennie answered 9/12, 2022 at 7:58 Comment(0)
S
-11

Have you considered using conditional compilation to control what happens during debug/deployment?

e.g.

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

Failing that, you want to expose the property so you can manipulate it, this is all part of the challenge of writing testable code, which is something I am currently wrestling myself :D

Edit

A big part of me would preference Blair's approach. This allows you to "hot plug" parts of the code to aid in testing. It all follows the design principle encapsulate what varies test code is no different to production code, its just no one ever sees it externally.

Creating and interface may seem like a lot of work for this example though (which is why I opted for conditional compilation).

Speculative answered 4/9, 2008 at 13:5 Comment(1)
wow you got hit hard on this answer. this is what I've been doing, although in a single place, where I call methods that replace DateTime's Now and Today etc.Broca

© 2022 - 2024 — McMap. All rights reserved.