ユニットテスト:DateTime.Now


164

「現在の時刻」がDateTime.Nowと異なることを期待する単体テストがあります。明らかに、コンピューターの時刻を変更したくありません。

これを達成するための最良の戦略は何ですか?


ユニットテストでは偽のデータを使用するため、現在の時刻に古い値を格納してdatetime.nowと比較します。これは簡単にできますよね。
Prashant Lakhlani 2010年

3
現在のDateTimeの抽象化を提供することは、テストやデバッグだけでなく、製品コードでも役立ちます。アプリケーションのニーズによって異なります。
Pavel Hodek

1
静的クラスを使用してDateTimeを取得しないでください
Daniel Little

そして、以下のVirtualTimeを使用した別の簡単なアプローチ
Samuel

機能する可能性があり、オーバーロードを追加できるオプションの1つは、を受け入れるメソッドのオーバーロードを作成することDateTimeです。これはあなたがテストするものであり、既存のメソッドは単にでオーバーロードを呼び出すだけnowです。
Phil Cooper、

回答:


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オブジェクトに置き換えることができます。Moqの使用例:

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

2
@IlyaChernomordik ILoggerや他の分野横断的な懸念を注入する必要はないので、時間プロバイダーを注入することは依然として最良のオプションです。
Mark Seemann、2014

5
@MikeK私の答えの最初の文が言うように:最善の戦略は、現在の時間を抽象化にラップし、その抽象化を消費者に注入することです。この回答の他のすべては2番目に優れた選択肢です。
Mark Seemann、2015年

3
こんにちは。私はNoob。このソリューションで人々がDateTime.UtcNowにアクセスするのをどのように防ぐのですか?TimeProviderを使用していると、見落としがちです。したがって、テストでバグが発生しますか?
Obbles

1
@Obblesコードレビュー(またはライブコードレビューの形式であるペアプログラミング)。コードベースなどをスキャンするツールを作成できると思いDateTime.UtcNowますが、とにかくコードレビューをお勧めします。
マークシーマン2018年

58

これらはすべて良い答えです、これは私が別のプロジェクトでやったことです:

使用法:

今日の実際の日付時刻を取得します

var today = SystemTime.Now().Date;

DateTime.Nowを使用する代わりに、SystemTime.Now()... を使用する必要があります。これは難しい変更ではありませんが、このソリューションはすべてのプロジェクトに理想的ではない場合があります。

タイムトラベリング(5年先に行こう)

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

Get our Fake "today"( 'today'から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
ありがとう、私はこの機能的なアプローチが好きです。今日(2年後)、TimeProviderクラスの変更されたバージョンをまだ使用しています(承認された回答を確認してください)。
ペドロ

7
@crabCRUSHERclamCOLI:ayende.com/blog/3408/dealing-with-time-in-testsからアイデアを得た場合、それにリンクすることは良いことです。
Johann Gerell 2012年

3
このコンセプトは、Guid生成のモックにも最適です(public static Func <Guid> NewGuid =()=> Guid.NewGuid();
mdwhatcott

2
この便利なメソッドを追加することもできます:public static void ResetDateTime(DateTime dateTimeNow){var timespanDiff = TimeSpan.FromTicks(DateTime.Now.Ticks-dateTimeNow.Ticks); 今=()=> DateTime.Now-timespanDiff; }
Pavel Hodek

17
非常に危険です。これは、並行して実行されている他の単体テストに影響を与える可能性があります。
Amete Blessed 2018

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とresharperでは動作しません。
オディス、2015年

Molesは現在サポートされておらず、Fakesmsdn.microsoft.com/ en
us/

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

Isolatorを使用して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

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がEntity Frameworkを意味していると思いますか?SystemTimeを使用しているオブジェクトはどのようなものですか?私の推測ではなく、あなたのエンティティの定期的なDateTimeオブジェクトを使用すると、それはあなたのアプリケーションに理にかなってどこSYSTEMTIMEを使用して設定する必要があり、あなたが実際のエンティティのSYSTEMTIMEへの参照を持っている
crabCRUSHERclamCOLLECTOR

1
はい、linqクエリとEntityFramework6を作成し、そのクエリでSystemTimeからUtcNowを参照するときにテストしました。説明されているように例外がありました。実装を変更した後、うまく機能します。
marcinn 2015年

このソリューションが大好きです。すごい!
mirind4

7

に依存するコードをテストするにはSystem.DateTime、をsystem.dllモックする必要があります。

これを実現するために知っている2つのフレームワークがあります。マイクロソフトの偽物スモック

Microsoftの偽物には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);
});

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

私はこれと同じ問題に遭遇しましたが、この問題を解決するマイクロソフトの研究プロジェクトを見つけました。

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.NowTypeMock ...

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

モックオブジェクト。

テストに適したNowを返すモックDateTime。


2

誰もが最も明白な方法の1つを提案していないことに驚いています。

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

これを行うクリーンな方法の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のインスタンスです。一部のDateTime.Now.ToVirtualTimeは、選択したDIコンテナーにセットアップされます

これは、クラスの委託者に注入する別の例です

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-in-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#の世界で眉を上げるかもしれません。どちらにしても、何が起こっているのかが非常にシンプルでわかりやすいので、私はこれを好みます。
擬似

0

同じ問題が発生しましたが、同じクラスで日付時刻の設定を使用しないでください。それはある日誤用につながる可能性があるためです。だから私はのようなプロバイダーを使用しました

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

ただし、1つのことを覚えておく必要があります。実際の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パッケージを探し、見つかった1で動作することはできません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メソッドの代わりに、パラメーターを使用して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)
        {

        }

    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.