Unit Testing: DateTime.Now

2019-01-02 16:57发布

I have some unit tests that expects the 'current time' to be different than DateTime.Now and I don't want to change the computer's time, obviously.

What's the best strategy to achieve this?

20条回答
高级女魔头
2楼-- · 2019-01-02 17:23

Good practice is, when DateTimeProvider implements IDisposable.

public class DateTimeProvider : IDisposable 
{ 
    [ThreadStatic] 
    private static DateTime? _injectedDateTime; 

    private DateTimeProvider() 
    { 
    } 

    /// <summary> 
    /// Gets DateTime now. 
    /// </summary> 
    /// <value> 
    /// The DateTime now. 
    /// </value> 
    public static DateTime Now 
    { 
        get 
        { 
            return _injectedDateTime ?? DateTime.Now; 
        } 
    } 

    /// <summary> 
    /// Injects the actual date time. 
    /// </summary> 
    /// <param name="actualDateTime">The actual date time.</param> 
    public static IDisposable InjectActualDateTime(DateTime actualDateTime) 
    { 
        _injectedDateTime = actualDateTime; 

        return new DateTimeProvider(); 
    } 

    public void Dispose() 
    { 
        _injectedDateTime = null; 
    } 
} 

Next, you can inject your fake DateTime for unit tests

    using (var date = DateTimeProvider.InjectActualDateTime(expectedDateTime)) 
    { 
        var bankAccount = new BankAccount(); 

        bankAccount.DepositMoney(600); 

        var lastTransaction = bankAccount.Transactions.Last(); 

        Assert.IsTrue(expectedDateTime.Equals(bankAccount.Transactions[0].TransactionDate)); 
    } 

See example Example of DateTimeProvider

查看更多
余生无你
3楼-- · 2019-01-02 17:24

One special note on mocking DateTime.Now with TypeMock...

The value of DateTime.Now must be placed into a variable for this to be mocked properly. For example:

This does not work:

if ((DateTime.Now - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))

However, this does:

var currentDateTime = DateTime.Now;
if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))
查看更多
春风洒进眼中
4楼-- · 2019-01-02 17:24

An alternative option that is not mentioned is to inject the current time to the dependent method:

public class DateTimeNowDependencyClass
{
    ...

    public void ImplicitTimeDependencyMethod(Obj arg0)
    {
        this.TimeDependencyMethod(DateTime.Now, arg0);
    }

    internal void TimeDependencyMethod(DateTime now, Obj arg1)
    {
        ...
    }

    ...
}

The internal variation is open to unit testing, parallel or not.

This is based on the principle that ImplicitTimeDependencyMethod is "too simple to break" (see: http://junit.sourceforge.net/doc/faq/faq.htm#best_3) so does not need to be included in unit test coverage. Though it should be touched in integration tests anyway.

Depending on the class' purpose it may be desirable to have both these methods public anyway.

查看更多
几人难应
5楼-- · 2019-01-02 17:26

These are all good answers, this is what I did on a different project:

Usage:

Get Today's REAL date Time

var today = SystemTime.Now().Date;

Instead of using DateTime.Now, you need to use SystemTime.Now()... It's not hard change but this solution might not be ideal for all projects.

Time Traveling (Lets go 5 years in the future)

SystemTime.SetDateTime(today.AddYears(5));

Get Our Fake "today" (will be 5 years from 'today')

var fakeToday = SystemTime.Now().Date;

Reset the date

SystemTime.ResetDateTime();

/// <summary>
/// Used for getting DateTime.Now(), time is changeable for unit testing
/// </summary>
public static class SystemTime
{
    /// <summary> Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging.
    /// </summary>
    public static Func<DateTime> Now = () => DateTime.Now;

    /// <summary> Set time to return when SystemTime.Now() is called.
    /// </summary>
    public static void SetDateTime(DateTime dateTimeNow)
    {
        Now = () =>  dateTimeNow;
    }

    /// <summary> Resets SystemTime.Now() to return DateTime.Now.
    /// </summary>
    public static void ResetDateTime()
    {
        Now = () => DateTime.Now;
    }
}
查看更多
初与友歌
6楼-- · 2019-01-02 17:27

I'm surprised no one has suggested one of the most obvious ways to go:

public class TimeDependentClass
{
    public void TimeDependentMethod(DateTime someTime)
    {
        if (GetCurrentTime() > someTime) DoSomething();
    }

    protected virtual DateTime GetCurrentTime()
    {
        return DateTime.Now; // or UtcNow
    }
}

Then you can simply override this method in your test double.

I also kind of like injecting a TimeProvider class in some cases, but for others, this is more than enough. I'd probably favor the TimeProvider version if you need to reuse this in several classes though.

EDIT: For anyone interested, this is called adding a "seam" to your class, a point where you can hook in to it's behavior to modify it (for testing purposes or otherwise) without actually having to change the code in the class.

查看更多
深知你不懂我心
7楼-- · 2019-01-02 17:29

Add a fake assembly for System (right click on System reference=>Add fake assembly).

And write into your test method:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
查看更多
登录 后发表回答