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

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


当前回答

好的做法是,当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示例

其他回答

在使用ITimeProvider时,我们被迫将它作为一个特殊的共享项目,必须从其他项目中引用它。但这使得依赖关系的控制变得复杂。

我们在。net框架中搜索ITimeProvider。我们搜索了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());
    }
}

如何注册

自发法克

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

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

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中找到样本

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

并在测试方法中写入:

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

好的做法是,当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示例

测试依赖于系统的代码。DateTime,系统.dll必须被模拟。

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

微软的假货需要visual studio 2012的最后通牒,直接从康普顿出来。

Smocks是一个开放源代码,非常容易使用。可以使用NuGet下载。

下面是System的一个模拟。DateTime:

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

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