Модульное тестирование: DateTime.Now
У меня есть некоторые модульные тесты, которые ожидают, что "текущее время" будет отличаться от DateTime.Now, и я, очевидно, не хочу изменять время компьютера.
Какова лучшая стратегия для достижения этой цели?
25 ответов
Лучшая стратегия - обернуть текущее время в абстракцию и внедрить ее в потребителя.
Кроме того, вы также можете определить абстракцию времени в качестве окружающего контекста:
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()
,
Это все хорошие ответы, это то, что я сделал в другом проекте:
Использование:
Получите РЕАЛЬНУЮ дату сегодня Время
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;
}
}
Родинки:
[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
}
Отказ от ответственности - я работаю на родинок
У вас есть несколько вариантов сделать это:
Используйте фальшивый фреймворк и используйте DateTimeService (реализуйте небольшой класс-обертку и внедрите его в производственный код). Реализация оболочки будет иметь доступ к DateTime, и в тестах вы сможете смоделировать класс оболочки.
Используйте Typemock Isolator, он может подделать DateTime.Now и не потребует от вас изменения тестируемого кода.
Используйте Moles, он также может подделать DateTime.Now и не потребует изменений в рабочем коде.
Некоторые примеры:
Класс Wrapper с использованием 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 напрямую с помощью Isolator:
[Test]
public void TestOfDateTime()
{
Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));
var result = new UnderTest().CalculateSomethingBasedOnDate();
}
Отказ от ответственности - я работаю в Typemock
Добавьте поддельную сборку для системы (щелкните правой кнопкой мыши ссылку на систему => Добавить поддельную сборку).
И напишите в свой метод испытаний:
using (ShimsContext.Create())
{
System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
MethodThatUsesDateTimeNow();
}
Чтобы проверить код, который зависит от System.DateTime
, system.dll
надо издеваться
Есть две основы, о которых я знаю, что делает это. Microsoft подделывает и смоки.
Подделки 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);
});
Вы можете изменить класс, который вы тестируете, чтобы использовать 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();
}
}
Что касается ответа @crabcrusherclamcollector, существует проблема при использовании этого подхода в запросах EF (System.NotSupportedException: тип узла выражения LINQ 'Invoke' не поддерживается в LINQ to Entities). Я изменил реализацию так:
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;
}
}
}
Поток безопасно 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();
}
Решение .NET 8 (встроенная библиотека базовых классов .NET 8)
В .NET 8 (в настоящее время в предварительной версии) это встроено в библиотеку классов!
Будет класс, похожий на поставщика классов с ответом Марка Зеемана.
Таким образом, в .NET 8 нет необходимости создавать собственный класс.
Применение
Внедрите свой объект и используйте.GetUtcNow()
или.GetLocalNow()
Зарегистрируйтесь вTimeProvider.System
для вашего кода (например, в Startup.cs или Program.cs):
services.AddSingleton(TimeProvider.System);
В тестах вы могли MockTimeProvider
класс, например с Moq
var timeProviderMock = new Mock<TimeProvider>();
timeProviderMock.Setup(t => t.GetLocalNow).Returns(new DateTime(2010, 3, 11));
var timeProvider = timeProviderMock.Object
Информация
См. также API для предоставления текущего системного времени #36617.
Обратите внимание: вам требуется .Net 8.0.0-preview.2 или более поздняя версия. См. раздел « Для .net 8». Как исправить Не удалось найти тип или имя пространства имен «TimeProvider»?
Одна особая заметка о насмешках 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))
Старый вопрос, но все еще актуален.
Мой подход - создать новый интерфейс и класс, чтобы обернуть 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.Guid.NewGuid()
и т.п.
См https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core для дальнейших примеров или получить https://www.nuget.org/packages/Stamina.Core пакет NuGet.
Я удивлен, что никто не предложил один из самых очевидных путей:
public class TimeDependentClass
{
public void TimeDependentMethod(DateTime someTime)
{
if (GetCurrentTime() > someTime) DoSomething();
}
protected virtual DateTime GetCurrentTime()
{
return DateTime.Now; // or UtcNow
}
}
Тогда вы можете просто переопределить этот метод в вашем двойном тесте.
Я также вроде как вводить TimeProvider
класс в некоторых случаях, но для других этого более чем достаточно. Я бы предпочел TimeProvider
версия, если вам нужно использовать это в нескольких классах, хотя.
РЕДАКТИРОВАТЬ: Для всех, кто заинтересован, это называется добавлением "шва" в ваш класс, точки, где вы можете подключиться к его поведению, чтобы изменить его (для целей тестирования или иным образом) без необходимости фактически изменять код в классе.
Я столкнулся с этой же проблемой, но нашел исследовательский проект от Microsoft, который решает эту проблему.
http://research.microsoft.com/en-us/projects/moles/
Moles - это легковесная структура для тестовых заглушек и обходов в.NET, основанная на делегатах. Кроты могут использоваться для обхода любого метода.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 в поставщика. Это было просто неправильно, и я чувствовал, что это слишком много только для тестирования. Я собираюсь реализовать это в моем личном проекте этим вечером.
Мы использовали статический объект SystemTime, но столкнулись с проблемами при выполнении параллельных модульных тестов. Я попытался использовать решение Хенка Ван Бойджена, но у меня возникли проблемы в порожденных асинхронных потоках, и в итоге я использовал 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
Хорошая практика, когда 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, который возвращает Now, подходящий для вашего теста.
Один из чистых способов сделать это - ввести VirtualTime. Это позволяет контролировать время. Сначала установите VirtualTime
Install-Package VirtualTime
Это позволяет, например, увеличивать время, которое перемещается в 5 раз при всех вызовах DateTime.Now или UtcNow.
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));
}
Вы можете проверить больше тестов здесь:
То, что дает вам расширение 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);
}
Альтернативный вариант, который не упоминается, - ввести текущее время в зависимый метод:
public class DateTimeNowDependencyClass
{
...
public void ImplicitTimeDependencyMethod(Obj arg0)
{
this.TimeDependencyMethod(DateTime.Now, arg0);
}
internal void TimeDependencyMethod(DateTime now, Obj arg1)
{
...
}
...
}
Внутренний вариант открыт для модульного тестирования, параллельного или нет.
Это основано на том принципе, что ImplicitTimeDependencyMethod
"слишком просто сломать" (см.: http://junit.sourceforge.net/doc/faq/faq.htm), поэтому его не нужно включать в покрытие модульных тестов. Хотя это должно быть затронуто в интеграционных тестах в любом случае.
В зависимости от цели класса может быть желательно, чтобы оба эти метода были общедоступными.
Я видел много вариантов создания имитируемых методов. Вот еще один. Вероятно, это неэффективно, но скорость не важна для моего приложения. Внутренний метод можно переопределить в тестовой среде:
public class TimeTools {
public static string DateNowTimestamp(){
return UtcNow().ToString("u").Replace(" ","T");
}
public static string DateNowFilekey(){
return UtcNow().ToString("yyyyMMdd_HHmmss");
}
public static long DateNowEpochSeconds(){
return new DateTimeOffset(UtcNow()).ToUnixTimeSeconds();
}
internal static Func<DateTime> UtcNow { get; set; } = () => DateTime.UtcNow;
}
Приятного аппетита!
Вот мой ответ на этот вопрос. Я комбинирую шаблон "Ambient Context" с 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;
}
}
}
У меня та же проблема, но я думал, что мы не должны использовать установленные даты и времени в одном классе. потому что это может привести к неправильному использованию в один прекрасный день. поэтому я использовал провайдера, как
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
Может быть, менее профессиональный, но более простое решение может быть сделать параметр DateTime в потребительском методе. Например, вместо метода make, такого как SampleMethod, сделать 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)
{
}
}
С помощью ITimeProvider
мы были вынуждены перенести его в специальный общий общий проект, на который должны ссылаться остальные проекты. Но это усложняло контроль зависимостей.
Мы искали ITimeProvider
в.NET Framework. Мы искали пакет 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);
}
У меня работает следующий код:
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)));