单元测试:DateTime.Now


164

我有一些单元测试期望'当前时间'与DateTime不同。现在,我显然不想更改计算机的时间。

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


在当前时间存储一些旧值,并将其与datetime进行比较。现在,由于单元测试使用的是伪数据,您可以轻松做到这一点,不是吗?
Prashant Lakhlani 2010年

3
提供当前DateTime的抽象不仅对测试和调试有用,而且在生产代码中也很有用-它取决于应用程序的需求。
帕维尔·霍德克

1
不要使用静态类获取DateTime
Daniel Little

以及下面使用VirtualTime的另一种简单方法
塞缪尔

一种可能有效并且可以添加重载的选项是创建接受的方法的重载DateTime。这将是您要测试的一种,现有方法将仅使用调用重载now
菲尔·库珀

Answers:


218

最好的策略是把当前时间的抽象和注入是抽象到消费者


另外,您还可以将时间抽象定义为环境上下文

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.Current用Test Double / Mock对象代替。使用最小起订量的示例:

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

但是,当在静态状态下进行单元测试时,请务必记住通过调用来拆开灯具TimeProvider.ResetToDefault()


7
在环境情况下,即使您拆解,如何使测试并行运行?
Ilya Chernomordik 2014年

2
@IlyaChernomordik您不必注入ILogger或其他跨切问题,因此注入时间提供者仍然是最佳选择。
Mark Seemann 2014年

5
@MikeK正如我回答的第一句话所说:最好的策略是将当前时间包装成一个抽象并将该抽象注入到消费者中。此答案中的所有其他内容都是次佳的选择。
马克·塞曼

3
你好 我菜鸟 如何使用此解决方案阻止人们访问DateTime.UtcNow?有人很容易错过使用TimeProvider的机会。从而导致测试错误?
Obbles

1
@Obbles代码审查(或成对编程,这是实时代码审查的一种形式)。我想可以编写一种工具来扫描代码库DateTime.UtcNow以及类似的代码,但是无论如何,代码审查都是一个好主意。
马克·塞曼

58

这些都是很好的答案,这就是我在另一个项目中所做的:

用法:

获取今天的真实日期时间

var today = SystemTime.Now().Date;

而不是使用DateTime.Now,您需要使用SystemTime.Now()...这不是很难的更改,但是此解决方案可能并不适合所有项目。

时间旅行(让我们在未来的5年内走出去)

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

“今天”获取我们的假货(距离“今天” 5年)

var fakeToday = SystemTime.Now().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;
    }
}

1
谢谢,我喜欢这个实用的方法。今天(两年后),我仍在使用TimeProvider类的修改版本(检查接受的答案),它的工作原理非常好。
Pedro

7
@crabCRUSHERclamCOLI:如果您从ayende.com/blog/3408/dealing-with-time-in-tests中获得想法,那么链接到它是一件好事。
约翰·杰雷尔

3
这个概念也非常适合模拟Guid生成(公共静态Func <Guid> NewGuid =()=> Guid.NewGuid();
mdwhatcott 2012年

2
您还可以添加此有用的方法:public static void ResetDateTime(DateTime dateTimeNow){var timespanDiff = TimeSpan.FromTicks(DateTime.Now.Ticks-dateTimeNow.Ticks); 现在=()=> DateTime.Now-timespanDiff; }
Pavel Hodek

17
非常危险。这可能会影响并行运行的其他单元测试。
Amete Blessed

24

痣:

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

免责声明-我从事痣工作


1
不幸的是,不适用于nunit和reshaper。
odyth

样痣看起来现在是不支持的,假货代替msdn.microsoft.com/en-us/library/...,毫无疑问,MS将不再是太快
斑马

17

您可以选择一些方法:

  1. 使用模拟框架并使用DateTimeService(实现一个小的包装器类,并将其注入到生产代码中)。包装器实现将访问DateTime,在测试中,您将能够模拟包装器类。

  2. 使用Typemock Isolator,它可以伪造DateTime.Now,并且不需要您更改被测代码。

  3. 使用Moles,它还可以伪造DateTime.Now,并且不需要更改生产代码。

一些例子:

使用Moq的包装器类:

[Test]
public void TestOfDateTime()
{
     var mock = new Mock<IDateTime>();
     mock.Setup(fake => fake.Now)
         .Returns(new DateTime(2000, 1, 1));

     var result = new UnderTest(mock.Object).CalculateSomethingBasedOnDate();
}

public class DateTimeWrapper : IDateTime
{
      public DateTime Now { get { return DateTime.Now; } }
}

使用隔离器直接伪造DateTime:

[Test]
public void TestOfDateTime()
{
     Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));

     var result = new UnderTest().CalculateSomethingBasedOnDate();
}

免责声明-我在Typemock工作


12

为系统添加一个假程序集(右键单击“系统参考” =>“添加假程序集”)。

并写入您的测试方法:

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

如果您可以使用Vs Premium或Ultimate,这是迄今为止最简单/最快的方法。
Rugdr 2015年

7

关于@crabcrusherclamcollector答案,在EF查询中使用该方法时会出现问题(System.NotSupportedException:LINQ to Entities不支持LINQ表达式节点类型'Invoke')。我将实现修改为:

public static class SystemTime
    {
        private static Func<DateTime> UtcNowFunc = () => DateTime.UtcNow;

        public static void SetDateTime(DateTime dateTimeNow)
        {
            UtcNowFunc = () => dateTimeNow;
        }

        public static void ResetDateTime()
        {
            UtcNowFunc = () => DateTime.UtcNow;
        }

        public static DateTime UtcNow
        {
            get
            {
                DateTime now = UtcNowFunc.Invoke();
                return now;
            }
        }
    }

我猜测EF是指实体框架吗?您正在使用SystemTime的对象是什么样的?我的猜测是您在实际实体上引用了SystemTime,相反,您应该在实体上使用常规DateTime对象,并在应用程序中有意义的地方使用SystemTime进行设置
crabCRUSHERclamCOLLECTOR

1
是的,我在创建linq查询和EntityFramework6并从SystemTime引用该查询UtcNow时进行了测试。如前所述,有例外。更改实现后,它可以很好地工作。
marcinn

我喜欢这个解决方案!大!
mirind4

7

为了测试依赖于一个代码System.DateTime,则system.dll必须嘲笑。

我知道有两个框架可以做到这一点。微软的假货Smocks

Microsoft假冒产品要求Visual Studio 2012最后通and,并且可以直接在compton之外工作。

Smocks是开源的,非常易于使用。可以使用NuGet下载。

下面显示了一个模拟System.DateTime

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

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});

5

安全SystemClock使用线程ThreadLocal<T>对我很有用。

ThreadLocal<T> 在.Net Framework v4.0和更高版本中可用。

/// <summary>
/// Provides access to system time while allowing it to be set to a fixed <see cref="DateTime"/> value.
/// </summary>
/// <remarks>
/// This class is thread safe.
/// </remarks>
public static class SystemClock
{
    private static readonly ThreadLocal<Func<DateTime>> _getTime =
        new ThreadLocal<Func<DateTime>>(() => () => DateTime.Now);

    /// <inheritdoc cref="DateTime.Today"/>
    public static DateTime Today
    {
        get { return _getTime.Value().Date; }
    }

    /// <inheritdoc cref="DateTime.Now"/>
    public static DateTime Now
    {
        get { return _getTime.Value(); }
    }

    /// <inheritdoc cref="DateTime.UtcNow"/>
    public static DateTime UtcNow
    {
        get { return _getTime.Value().ToUniversalTime(); }
    }

    /// <summary>
    /// Sets a fixed (deterministic) time for the current thread to return by <see cref="SystemClock"/>.
    /// </summary>
    public static void Set(DateTime time)
    {
        if (time.Kind != DateTimeKind.Local)
            time = time.ToLocalTime();

        _getTime.Value = () => time;
    }

    /// <summary>
    /// Resets <see cref="SystemClock"/> to return the current <see cref="DateTime.Now"/>.
    /// </summary>
    public static void Reset()
    {
        _getTime.Value = () => DateTime.Now;
    }
}

用法示例:

[TestMethod]
public void Today()
{
    SystemClock.Set(new DateTime(2015, 4, 3));

    DateTime expectedDay = new DateTime(2015, 4, 2);
    DateTime yesterday = SystemClock.Today.AddDays(-1D);
    Assert.AreEqual(expectedDay, yesterday);

    SystemClock.Reset();
}

1
+!线程本地应该避免并行测试执行和可能覆盖当前Now实例的多个测试的问题。
Hux

1
尝试使用此方法,但跨线程存在问题,因此使用了类似的主体,但使用的AsyncLocal不是ThreadLocal
-rrrr

@rrrr:您能解释一下您遇到的问题吗?
Henk van Boeijen

@HenkvanBoeijen当然,我们在设置时间时遇到了问题,然后我们在等待异步方法。Console.WriteLine(SystemClock.Now); SystemClock.Set(new DateTime(2017, 01, 01)); Console.WriteLine(SystemClock.Now); await Task.Delay(1000); Console.WriteLine(SystemClock.Now);
rrrr

@HenkvanBoeijen我使用链接
rrrr

3

我遇到了同样的问题,但是找到了Microsoft的一个解决该问题的研究项目。

http://research.microsoft.com/en-us/projects/moles/

Moles是一个轻量级框架,用于基于委托的.NET中的测试存根和弯路。Moles可用于绕过任何.NET方法,包括密封类型的非虚拟/静态方法

// Let's detour DateTime.Now
MDateTime.NowGet = () => new DateTime(2000,1, 1);

if (DateTime.Now == new DateTime(2000, 1, 1);
{
    throw new Exception("Wahoo we did it!");
}

该示例代码是从原始代码修改而来的。

我做了其他建议,然后将DateTime抽象为提供程序。只是感觉不对,我觉得这仅仅是为了测试而已。我今晚将在我的个人项目中实现这一点。


2
顺便说一句,这仅适用于VS2010。当我发现Moles现在是VS2012的假货时,我感到很沮丧,它仅适用于Premium和Ultimate Visual Studios。VS 2012 Professional不假。所以我从来没有实际使用过。:(
鲍比·加农炮

3

关于DateTime.Now使用TypeMock进行模拟的特别说明...

DateTime.Now必须将的值放入变量中,以便正确模拟该变量。例如:

这不起作用:

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

但是,这样做:

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

2

模拟对象。

模拟的DateTime返回适合于测试的Now。


2

令人惊讶的是,没有人提出最显而易见的方法之一:

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

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

然后,您可以在测试double中简单地覆盖此方法。

TimeProvider在某些情况下,我还喜欢注入一个类,但是对于其他情况,这绰绰有余。TimeProvider如果您需要在多个类中重用此版本,则可能更喜欢该版本。

编辑:对于任何感兴趣的人,这都被称为在类中添加“缝”,这是您可以挂钩到其行为以对其进行修改(出于测试目的或其他目的)的点,而无需实际更改类中的代码。


1

良好的做法是,当DateTimeProvider实现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; 
    } 
} 

接下来,您可以注入伪造的DateTime进行单元测试

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

请参见DateTimeProvider的示例


1

一种干净的方法是注入VirtualTime。它允许您控制时间。首先安装VirtualTime

Install-Package VirtualTime

例如,这可以使所有DateTime.Now或UtcNow调用的时间缩短5倍

var DateTime = DateTime.Now.ToVirtualTime(5);

使时间变慢,例如慢5倍,

var DateTime = DateTime.Now.ToVirtualTime(0.5);

让时间停滞不前

var DateTime = DateTime.Now.ToVirtualTime(0);

向后移动尚未经过测试

这是一个示例测试:

[TestMethod]
public void it_should_make_time_move_faster()
{
    int speedOfTimePerMs = 1000;
    int timeToPassMs = 3000;
    int expectedElapsedVirtualTime = speedOfTimePerMs * timeToPassMs;
    DateTime whenTimeStarts = DateTime.Now;
    ITime time = whenTimeStarts.ToVirtualTime(speedOfTimePerMs);
    Thread.Sleep(timeToPassMs);
    DateTime expectedTime = DateTime.Now.AddMilliseconds(expectedElapsedVirtualTime - timeToPassMs);
    DateTime virtualTime = time.Now;

    Assert.IsTrue(TestHelper.AreEqualWithinMarginOfError(expectedTime, virtualTime, MarginOfErrorMs));
}

您可以在此处查看更多测试:

https://github.com/VirtualTime/VirtualTime/blob/master/VirtualTimeLib.Tests/when_virtual_time_is_used.cs

DateTime.Now.ToVirtualTime扩展为您提供的是ITime的实例,您可以将该实例传递给依赖于ITime的方法/类。在您选择的DI容器中设置了一些DateTime.Now.ToVirtualTime

这是注入类委托人的另一个示例

public class AlarmClock
{
    private ITime DateTime;
    public AlarmClock(ITime dateTime, int numberOfHours)
    {
        DateTime = dateTime;
        SetTime = DateTime.UtcNow.AddHours(numberOfHours);
        Task.Run(() =>
        {
            while (!IsAlarmOn)
            {
                IsAlarmOn = (SetTime - DateTime.UtcNow).TotalMilliseconds < 0;
            }
        });
    }
    public DateTime SetTime { get; set; }
    public bool IsAlarmOn { get; set; }
}

[TestMethod]
public void it_can_be_injected_as_a_dependency()
{
    //virtual time has to be 1000*3.75 faster to get to an hour 
    //in 1000 ms real time
    var dateTime = DateTime.Now.ToVirtualTime(1000 * 3.75);
    var numberOfHoursBeforeAlarmSounds = 1;
    var alarmClock = new AlarmClock(dateTime, numberOfHoursBeforeAlarmSounds);
    Assert.IsFalse(alarmClock.IsAlarmOn);
    System.Threading.Thread.Sleep(1000);
    Assert.IsTrue(alarmClock.IsAlarmOn);
}

在Ayende上还有很多关于依赖于DateTime的测试代码的详细信息,这里为ayende.com/blog/3408/dealing-with-time-tests
Samuel

1

我们使用了一个静态的SystemTime对象,但是在运行并行单元测试时遇到了问题。我尝试使用Henk van Boeijen的解决方案,但在生成的异步线程中遇到了问题,最终以类似于下面的方式使用AsyncLocal:

public static class Clock
{
    private static Func<DateTime> _utcNow = () => DateTime.UtcNow;

    static AsyncLocal<Func<DateTime>> _override = new AsyncLocal<Func<DateTime>>();

    public static DateTime UtcNow => (_override.Value ?? _utcNow)();

    public static void Set(Func<DateTime> func)
    {
        _override.Value = func;
    }

    public static void Reset()
    {
        _override.Value = null;
    }
}

源自https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08


1

一个老问题,但仍然有效。

我的方法是创建一个新的接口和类来包装System.DateTime.Now调用

public interface INow
{
    DateTime Execute();
}

public sealed class Now : INow
{
    public DateTime Execute()
    {
        return DateTime.Now
    }
}

该接口可以注入需要获取当前日期和时间的任何类中。在此示例中,我有一个向当前日期和时间添加时间跨度的类(可单元测试的System.DateTime.Now.Add(TimeSpan))

public interface IAddTimeSpanToCurrentDateAndTime
{
    DateTime Execute(TimeSpan input);
}

public class AddTimeSpanToCurrentDateAndTime : IAddTimeSpanToCurrentDateAndTime
{
    private readonly INow _now;

    public AddTimeSpanToCurrentDateAndTime(INow now)
    {
        this._now = now;
    }

    public DateTime Execute(TimeSpan input)
    {
        var currentDateAndTime = this._now.Execute();

        return currentDateAndTime.Add(input);
    }
}

可以编写测试以确保其正确运行。我正在使用NUnit和Moq,但是任何测试框架都可以

public class Execute
{
    private Moq.Mock<INow> _nowMock;

    private AddTimeSpanToCurrentDateAndTime _systemUnderTest;

    [SetUp]
    public void Initialize()
    {
        this._nowMock = new Moq.Mock<INow>(Moq.MockBehavior.Strict);

        this._systemUnderTest = AddTimeSpanToCurrentDateAndTime(
            this._nowMock.Object);
    }

    [Test]
    public void AddTimeSpanToCurrentDateAndTimeExecute0001()
    {
        // arrange

        var input = new TimeSpan(911252);

        // arrange : mocks

        this._nowMock
            .Setup(a => a.Execute())
            .Returns(new DateTime(348756););

        // arrange : expected

        var expected = new DateTime(911252 + 348756);

        // act

        var actual = this._systemUnderTest.Execute(input).Result;

        // assert

        Assert.Equals(actual, expected);
    }
}

这种模式将用于依赖于外部因素,像任何功能工作System.Random.Next()System.DateTime.Now.UtcNowSystem.Guid.NewGuid()等。

有关更多示例,请参见https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core或获取https://www.nuget.org/packages/Stamina.Core nuget包。


1

您可以将要测试的类更改为使用Func<DateTime>将通过其构造函数参数传递的类,因此,当您在真实代码中创建该类的实例时,可以传递() => DateTime.UtcNow给该Func<DateTime>参数,然后在测试中可以传递时间希望测试。

例如:

    [TestMethod]
    public void MyTestMethod()
    {
        var instance = new MyClass(() => DateTime.MinValue);
        Assert.AreEqual(instance.MyMethod(), DateTime.MinValue);
    } 

    public void RealWorldInitialization()
    {
        new MyClass(() => DateTime.UtcNow);
    }

    class MyClass
    {
        private readonly Func<DateTime> _utcTimeNow;

        public MyClass(Func<DateTime> UtcTimeNow)
        {
            _utcTimeNow = UtcTimeNow;
        }

        public DateTime MyMethod()
        {
            return _utcTimeNow();
        }
    }

1
我喜欢这个答案。从我的收集来看,这可能在C#世界中引起了极大的关注,在C#世界中,人们更多地关注类/对象。无论哪种方式,我都喜欢这样做,因为它对我来说非常简单明了。

0

我遇到了同样的问题,但是我想我们不应该在同一类上使用设置的datetime东西。因为它可能导致滥用一天。所以我已经使用了像

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

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

MockSamples中找到样本


0

这是我对这个问题的回答。我将“环境上下文”模式与IDisposable相结合。因此,您可以在常规程序代码中使用DateTimeProvider.Current,并在测试中使用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;
        }
    }
}

0

使用ITimeProvider我们被迫将其带入一个特殊的共享公共项目,该项目必须从其他项目中引用。但是,这使对依赖项的控制变得复杂

我们ITimeProvider在.NET框架中搜索。我们搜索的NuGet包,发现一个不能一起工作DateTimeOffset

因此,我们提出了自己的解决方案,该解决方案仅取决于标准库的类型。我们正在使用的实例Func<DateTimeOffset>

如何使用

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

如何注册

Autofac

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

对于以后的编辑:请在此处附加您的案例)。

如何进行单元测试

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

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

    Assert.AreEqual(expected, actual);
}

0

也许可以使用使用者方法在DateTime参数上使用不那么专业但更简单的解决方案。例如,代替带有SampleMethod的make方法,使用带有参数的make SampleMethod1.SampleMethod1的测试更加容易

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

        }

    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.