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

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


当前回答

下面的代码为我工作:

  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 TimeDependentClass
{
    public void TimeDependentMethod(DateTime someTime)
    {
        if (GetCurrentTime() > someTime) DoSomething();
    }

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

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

在某些情况下,我还喜欢注入一个TimeProvider类,但对于其他情况,这就足够了。不过,如果需要在多个类中重用TimeProvider版本,我可能更喜欢它。

编辑:对于任何感兴趣的人来说,这被称为向类中添加“接缝”,在这个点上,您可以钩入它的行为来修改它(用于测试目的或其他),而无需实际更改类中的代码。

我们使用的是静态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

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

我的方法是创建一个新的接口和类来包装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。UtcNow, system . guide . newguid()等。

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

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

并在测试方法中写入:

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

模拟对象。

一个模拟DateTime,返回适合您的测试的Now。