我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。

实现这一目标的最佳策略是什么?


当前回答

为系统添加一个假程序集(右键单击System reference=>添加假程序集)。

并在测试方法中写入:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}

其他回答

最好的策略是将当前时间封装在一个抽象中,并将该抽象注入到使用者中。


或者,你也可以将时间抽象定义为环境上下文:

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
       get { return TimeProvider.current; }
       set 
       {
           if (value == null)
           {
               throw new ArgumentNullException("value");
           }
           TimeProvider.current = value; 
       }
   }

   public abstract DateTime UtcNow { get; }

   public static void ResetToDefault()
   {    
       TimeProvider.current = DefaultTimeProvider.Instance;
   }            
}

这将使你能够像这样消费它:

var now = TimeProvider.Current.UtcNow;

在单元测试中,您可以替换TimeProvider。当前带有Test Double/Mock对象。使用Moq的例子:

var timeMock = new Mock<TimeProvider>();
timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11));
TimeProvider.Current = timeMock.Object;

但是,当使用静态状态进行单元测试时,始终记得通过调用TimeProvider.ResetToDefault()来删除fixture。

以下是我对这个问题的回答。我将“环境上下文”模式与IDisposable结合起来。你可以使用DateTimeProvider。当前在您的正常程序代码和测试中,您使用using语句覆盖范围。

using System;
using System.Collections.Immutable;


namespace ambientcontext {

public abstract class DateTimeProvider : IDisposable
{
    private static ImmutableStack<DateTimeProvider> stack = ImmutableStack<DateTimeProvider>.Empty.Push(new DefaultDateTimeProvider());

    protected DateTimeProvider()
    {
        if (this.GetType() != typeof(DefaultDateTimeProvider))
            stack = stack.Push(this);
    }

    public static DateTimeProvider Current => stack.Peek();
    public abstract DateTime Today { get; }
    public abstract DateTime Now {get; }

    public void Dispose()
    {
        if (this.GetType() != typeof(DefaultDateTimeProvider))
            stack = stack.Pop();
    }

    // Not visible Default Implementation 
    private class DefaultDateTimeProvider : DateTimeProvider {
        public override DateTime Today => DateTime.Today; 
        public override DateTime Now => DateTime.Now; 
    }
}
}

下面是如何在单元测试中使用上述DateTimeProvider

using System;
using Xunit;

namespace ambientcontext
{
    public class TestDateTimeProvider
    {
        [Fact]
        public void TestDateTime()
        {
            var actual = DateTimeProvider.Current.Today;
            var expected = DateTime.Today;

            Assert.Equal<DateTime>(expected, actual);

            using (new MyDateTimeProvider(new DateTime(2012,12,21)))
            {
                Assert.Equal(2012, DateTimeProvider.Current.Today.Year);

                using (new MyDateTimeProvider(new DateTime(1984,4,4)))
                {
                    Assert.Equal(1984, DateTimeProvider.Current.Today.Year);    
                }

                Assert.Equal(2012, DateTimeProvider.Current.Today.Year);
            }

            // Fall-Back to Default DateTimeProvider 
            Assert.Equal<int>(expected.Year,  DateTimeProvider.Current.Today.Year);
        }

        private class MyDateTimeProvider : DateTimeProvider 
        {
            private readonly DateTime dateTime; 

            public MyDateTimeProvider(DateTime dateTime):base()
            {
                this.dateTime = dateTime; 
            }

            public override DateTime Today => this.dateTime.Date;

            public override DateTime Now => this.dateTime;
        }
    }
}

摩尔数:

[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
}

免责声明-我工作的鼹鼠

下面的代码为我工作:

  bizDeedMock.Verify(p => p.SetDeed(It.Is<DsPostList>(x => x.PostLists[0].registerDate.Year == DateTime.Now.Year)));
            bizDeedMock.Verify(p => p.SetDeed(It.Is<DsPostList>(x => x.PostLists[0].registerDate.Month == DateTime.Now.Month)));
            bizDeedMock.Verify(p => p.SetDeed(It.Is<DsPostList>(x => x.PostLists[0].registerDate.Day == DateTime.Now.Day)));

我也有同样的问题,但我在想我们不应该在同一类上使用设置日期时间的东西。因为有一天可能会导致滥用。我使用了提供商

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()
    {       
    }
}

对于测试,在测试项目中创建了一个助手来处理设置的事情,

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;
    }
}

在代码

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

在测试中

[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);
}

但需要记住一件事,有时真正的DateTime和提供程序的DateTime并不相同

[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));
}

我假设差异是maximum TimeSpan.FromMilliseconds(0.00002)。但大多数时候甚至更少

在MockSamples中找到样本