Mock DateTime.UtcNow 用于 C# 中的单元测试

问题描述 投票:0回答:1

我正在尝试使用

Pose
在单元测试中模拟 DateTime.UtcNow,但似乎在 .NET 6 中不起作用。 代码如下所示:

// Arrange
...
var created = DateTime.UtcNow;
var expectedRequest = new ExpectedRequest()
{
    Created = created
    ...
};

var shim = Shim.Replace(() => DateTime.UtcNow).With(() => created);

PoseContext.Isolate(() =>
{
    // Act
    var mappedRequest = request.ToExpectedRequest();

    // Assert
    mappedRequest.Should().BeEquivalentTo(expectedRequest);
}, shim);
...

问题是,当达到

PoseContext.Isolate(()
时,单元测试会卡住,当我尝试停止单元测试时,Visual Studio 会冻结并自行重新启动。

还有其他类似于Pose的选项或库吗?

我知道最常见的方法是引入应用程序逻辑将使用的接口,而不是直接使用

DateTime.UtcNow
属性,但我不想为此注入新服务,并且我正在尝试在不修改实现的情况下找到解决方案。

编辑: 有多种方法可以“解决”

DateTime
的这个模拟问题,可以在这里这里找到,但我选择环境上下文方法

单元测试现在看起来像这样:

// Arrange
...
using var context = new DateTimeProviderContext(DateTime.UtcNow);
var expectedRequest = new ExpectedRequest()
{
    Created = DateTimeProvider.UtcNow
    ...
};

// Act
var mappedRequest = request.ToExpectedRequest();

// Assert
mappedRequest.Should().BeEquivalentTo(expectedRequest);
...

在实现中唯一要做的就是使用

Created = DateTimeProvider.UtcNow
而不是
Created = DateTime.UtcNow

我将在这里添加这两个类

DateTimeProvider
DateTimeProviderContext
,以防有人需要它们。

public static class DateTimeProvider
{
    public static DateTime Now
        => DateTimeProviderContext.Current == null
        ? DateTime.Now
        : DateTimeProviderContext.Current.ContextDateTime;

    public static DateTime UtcNow => Now.ToUniversalTime();
}
public class DateTimeProviderContext : IDisposable
{
    internal DateTime ContextDateTime;
    private static readonly ThreadLocal<Stack> _threadScopeStack = new(() => new Stack());
    private bool _isDisposed = false;

    public DateTimeProviderContext(DateTime contextDateTime)
    {
        ContextDateTime = contextDateTime;
        _threadScopeStack.Value?.Push(this);
    }

    public static DateTimeProviderContext? Current
    {
        get
        {
            if (_threadScopeStack.Value?.Count == 0)
            {
                return null;
            }
            else
            {
                return _threadScopeStack.Value?.Peek() as DateTimeProviderContext;
            }
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed && disposing)
        {
            _threadScopeStack.Value?.Pop();
        }
        _isDisposed = true;
    }
}
c# unit-testing datetime .net-6.0
1个回答
0
投票

我知道这个问题已经有将近两年了,但是我遇到了同样的问题并找到了一个解决方案,这对我有用。

使用 Pose 时,

Assert
- 不应将语句隔离在
PoseContext
内。

Pose
假装 DateTime 看起来像这样:

// Arrange
var fixture = new ClassUnderTest();
var timeStamp = new DateTime(2024, 4, 15, 15, 16, 17, 745, DateTimeKind.Utc);
var expected = "someExpectedValue";
var actual = string.Empty;

// Act
PoseContext.Isolate(() =>
{
    actual = fixture.DoSomething();
}, dateTimeShim);

// Assert
Assert.AreEqual(expected, result);

注意

PoseContext.Isolate
内部如何仅调用被测试的方法。

在您的情况下,测试将如下所示:

// Arrange
...
var created = DateTime.UtcNow;
var expectedRequest = new ExpectedRequest()
{
    Created = created
    ...
};

var shim = Shim.Replace(() => DateTime.UtcNow).With(() => created);
ExpectedRequest mappedRequest = null;

PoseContext.Isolate(() =>
{
    // Act
    mappedRequest = request.ToExpectedRequest();
}, shim);

// Assert
mappedRequest.Should().BeEquivalentTo(expectedRequest);
...
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.