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:38

To test a code that depends on the System.DateTime, the system.dll must be mocked.

There are two framework that I know of that does this. Microsoft fakes and Smocks.

Microsoft fakes require visual studio 2012 ultimatum and works straight out of the compton.

Smocks is an open source and very easy to use. It can be downloaded using NuGet.

The following shows a mock of System.DateTime:

Smock.Run(context =>
{
  context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});
查看更多
呛了眼睛熬了心
3楼-- · 2019-01-02 17:38

I got same problem, but i was thinking we should not use the set datetime things on the same class. because it could lead to misuse one day. so i have used the provider like

public class DateTimeProvider
{
    protected static DateTime? DateTimeNow;
    protected static DateTime? DateTimeUtcNow;

    public DateTime Now
    {
        get
        {
            return DateTimeNow ?? System.DateTime.Now;
        }
    }

    public DateTime UtcNow
    {
        get
        {
            return DateTimeUtcNow ?? System.DateTime.UtcNow;
        }
    }

    public static DateTimeProvider DateTime
    {
        get
        {
            return new DateTimeProvider();
        }
    }

    protected DateTimeProvider()
    {       
    }
}

For tests, at test project made a helper which will deal with set things,

public class MockDateTimeProvider : DateTimeProvider
{
    public static void SetNow(DateTime now)
    {
        DateTimeNow = now;
    }

    public static void SetUtcNow(DateTime utc)
    {
        DateTimeUtcNow = utc;
    }

    public static void RestoreAsDefault()
    {
        DateTimeNow = null;
        DateTimeUtcNow = null;
    }
}

on code

var dateTimeNow = DateTimeProvider.DateTime.Now         //not DateTime.Now
var dateTimeUtcNow = DateTimeProvider.DateTime.UtcNow   //not DateTime.UtcNow

and on tests

[Test]
public void Mocked_Now()
{
    DateTime now = DateTime.Now;
    MockDateTimeProvider.SetNow(now);    //set to mock
    Assert.AreEqual(now, DateTimeProvider.DateTime.Now);
    Assert.AreNotEqual(now, DateTimeProvider.DateTime.UtcNow);
}

[Test]
public void Mocked_UtcNow()
{
    DateTime utcNow = DateTime.UtcNow;
    MockDateTimeProvider.SetUtcNow(utcNow);   //set to mock
    Assert.AreEqual(utcNow, DateTimeProvider.DateTime.UtcNow);
    Assert.AreNotEqual(utcNow, DateTimeProvider.DateTime.Now);
}

But need to remember one thing, sometime the real DateTime and provider's DateTime doesn't act same

[Test]
public void Now()
{
    Assert.AreEqual(DateTime.Now.Kind, DateTimeProvider.DateTime.Now.Kind);
    Assert.LessOrEqual(DateTime.Now, DateTimeProvider.DateTime.Now);
    Assert.LessOrEqual(DateTimeProvider.DateTime.Now - DateTime.Now, TimeSpan.FromMilliseconds(1));
}

I assumed the deference would be maximum TimeSpan.FromMilliseconds(0.00002). But most of the time it's even less

Find the sample at MockSamples

查看更多
孤独总比滥情好
4楼-- · 2019-01-02 17:39

Moles:

[Test]  
public void TestOfDateTime()  
{  
      var firstValue = DateTime.Now;
      MDateTime.NowGet = () => new DateTime(2000,1,1);
      var secondValue = DateTime.Now;
      Assert(firstValue > secondValue); // would be false if 'moleing' failed
}

Disclaimer - I work on Moles

查看更多
伤终究还是伤i
5楼-- · 2019-01-02 17:42

Using ITimeProvider we were forced to take it into special shared common project that must be referenced from the rest of other projects. But this complicated the control of dependencies.

We searched for the ITimeProvider in the .NET framework. We searched for the NuGet package, and found one that can't work with DateTimeOffset.

So we came up with our own solution, which depends only on the types of the standard library. We're using an instance of Func<DateTimeOffset>.

How to use

public class ThingThatNeedsTimeProvider
{
    private readonly Func<DateTimeOffset> now;
    private int nextId;

    public ThingThatNeedsTimeProvider(Func<DateTimeOffset> now)
    {
        this.now = now;
        this.nextId = 1;
    }

    public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple()
    {
        return (nextId++, now());
    }
}

How to register

Autofac

builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);

(For future editors: append your cases here).

How to unit test

public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
    DateTimeOffset expected = CreateRandomDateTimeOffset();
    DateTimeOffset StubNow() => expected;
    var thing = new ThingThatNeedsTimeProvider(StubNow);

    var (_, actual) = thing.MakeIllustratingTuple();

    Assert.AreEqual(expected, actual);
}
查看更多
初与友歌
6楼-- · 2019-01-02 17:45

Maybe less Professional but simpler solution could be make a DateTime parameter at consumer method.For example instead of make method like SampleMethod , make SampleMethod1 with parameter.Testing of SampleMethod1 is easier

public void SampleMethod()
    {
        DateTime anotherDateTime = DateTime.Today.AddDays(-10);
        if ((DateTime.Now-anotherDateTime).TotalDays>10)
        {

        }
    }
    public void SampleMethod1(DateTime dateTimeNow)
    {
        DateTime anotherDateTime = DateTime.Today.AddDays(-10);
        if ((dateTimeNow - anotherDateTime).TotalDays > 10)
        {

        }

    }
查看更多
谁念西风独自凉
7楼-- · 2019-01-02 17:47

Mock Objects.

A mock DateTime that returns a Now that's appropriate for your test.

查看更多
登录 后发表回答