You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core Web API集成测试中自定义DateTime.Now?

问题描述

我有一个项目,其中的集成测试需要使用自定义的DateTime.Now来验证订单配送时间逻辑。相关代码如下:

检查订单配送时间的静态方法

public static ErrorResult CheckAvailabiliteOfOrderDeliveryInCurrentTime(string openingTime,string closingTime)
{
    var currentTimeString = System.DateTime.Now.ToString("HH:mm:ss"); 

    if (DateTime.Parse(openingTime + ":00") > DateTime.Parse(closingTime + ":00"))
    {
        if ((DateTime.Parse(currentTimeString) <= DateTime.Parse("23:59:00") && 
             DateTime.Parse(currentTimeString) > DateTime.Parse(openingTime + ":00")) || 
            (DateTime.Parse(currentTimeString) < DateTime.Parse(closingTime + ":00") && 
             DateTime.Parse(currentTimeString) >= DateTime.Parse("00:00:00")))
        {
            return ErrorResult.None;
        }
        else
        {
            return ErrorResult.OutOfDeliveryTime;
        }
    }
    else
    {
        if (DateTime.Parse(currentTimeString) < DateTime.Parse(closingTime + ":00") && 
            DateTime.Parse(currentTimeString) > DateTime.Parse(openingTime + ":00"))
        {
            return ErrorResult.None;
        }
        else
        {
            return ErrorResult.OutOfDeliveryTime;
        }
    }
}

调用该方法的Handler

public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
    var validator = new CreateOrderCommandValidator();
    var result = await validator.ValidateAsync(request, cancellationToken).ConfigureAwait(false);

    if (!result.IsValid) 
         throw new ValidationException(result);

    var foodBusiness = await _context.FoodBusinesses.FindAsync(Guid.Parse(request.FoodBusinessId));

    if (foodBusiness == null)
        throw new NotFoundException(nameof(FoodBusiness), request.FoodBusinessId);

    if (request.Type == OrderTypes.Delivery)
    {   
        var isOutdeliveryTime = DateTimeHelpers.CheckAvailabiliteOfOrderDeliveryInCurrentTime(foodBusiness.OpeningTime, foodBusiness.ClosingTime);

        if (isOutdeliveryTime == ErrorResult.OutOfDeliveryTime)
        {
            var newOrder = new OrderDto();
            newOrder.errorDeliveryTimeAvailabilite = ErrorResult.OutOfDeliveryTime;

            return _mapper.Map<OrderDto>(newOrder);
        }
        else
        {
            var newOrder = await ExecuteOrderOperations(request, cancellationToken, foodBusiness);
            return _mapper.Map<OrderDto>(newOrder);
        }
    }
    else
    {
        var newOrder = await ExecuteOrderOperations(request, cancellationToken, foodBusiness);
        return _mapper.Map<OrderDto>(newOrder);
    }
}

默认应用直接使用System.DateTime.Now,请问在集成测试中如何替换成自定义的时间?


解决方案

要在集成测试中自定义DateTime.Now,核心是将时间获取逻辑抽象成可注入的服务,避免直接依赖静态的DateTime.Now,这样测试时就能替换成模拟的时间提供者。

1. 定义时间服务接口

创建接口封装时间获取逻辑,实现解耦:

public interface IDateTimeProvider
{
    DateTime Now { get; }
}

2. 实现生产环境的时间服务

编写面向生产环境的实现,直接返回系统当前时间:

public class SystemDateTimeProvider : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
}

3. 重构静态方法,依赖时间服务

将原来的静态方法改为类的实例方法,通过构造函数注入时间服务:

public class DateTimeHelpers
{
    private readonly IDateTimeProvider _dateTimeProvider;

    public DateTimeHelpers(IDateTimeProvider dateTimeProvider)
    {
        _dateTimeProvider = dateTimeProvider;
    }

    public ErrorResult CheckAvailabiliteOfOrderDeliveryInCurrentTime(string openingTime, string closingTime)
    {
        var currentTime = _dateTimeProvider.Now;
        var currentTimeString = currentTime.ToString("HH:mm:ss"); 

        // 原有时间判断逻辑保持不变
        if (DateTime.Parse(openingTime + ":00") > DateTime.Parse(closingTime + ":00"))
        {
            if ((DateTime.Parse(currentTimeString) <= DateTime.Parse("23:59:00") && 
                 DateTime.Parse(currentTimeString) > DateTime.Parse(openingTime + ":00")) || 
                (DateTime.Parse(currentTimeString) < DateTime.Parse(closingTime + ":00") && 
                 DateTime.Parse(currentTimeString) >= DateTime.Parse("00:00:00")))
            {
                return ErrorResult.None;
            }
            else
            {
                return ErrorResult.OutOfDeliveryTime;
            }
        }
        else
        {
            if (DateTime.Parse(currentTimeString) < DateTime.Parse(closingTime + ":00") && 
                DateTime.Parse(currentTimeString) > DateTime.Parse(openingTime + ":00"))
            {
                return ErrorResult.None;
            }
            else
            {
                return ErrorResult.OutOfDeliveryTime;
            }
        }
    }
}

4. 修改Handler,注入依赖

在Handler的构造函数中注入DateTimeHelpers:

public class CreateOrderCommandHandler
{
    private readonly YourDbContext _context;
    private readonly IMapper _mapper;
    private readonly DateTimeHelpers _dateTimeHelpers;

    public CreateOrderCommandHandler(YourDbContext context, IMapper mapper, DateTimeHelpers dateTimeHelpers)
    {
        _context = context;
        _mapper = mapper;
        _dateTimeHelpers = dateTimeHelpers;
    }

    public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        // 原有验证、查询逻辑保持不变

        if (request.Type == OrderTypes.Delivery)
        {   
            var isOutdeliveryTime = _dateTimeHelpers.CheckAvailabiliteOfOrderDeliveryInCurrentTime(foodBusiness.OpeningTime, foodBusiness.ClosingTime);

            // 后续逻辑保持不变
        }
        // 其余逻辑不变
    }
}

5. 注册服务到依赖注入容器

在应用启动配置(如Program.cs)中注册服务:

// .NET 6+ Program.cs
builder.Services.AddSingleton<IDateTimeProvider, SystemDateTimeProvider>();
builder.Services.AddTransient<DateTimeHelpers>();

6. 集成测试中替换时间服务

创建可自定义时间的模拟实现:

public class MockDateTimeProvider : IDateTimeProvider
{
    public DateTime Now { get; set; }

    public MockDateTimeProvider(DateTime customTime)
    {
        Now = customTime;
    }
}

在测试中替换服务容器中的时间提供者:

[TestClass]
public class CreateOrderIntegrationTests
{
    private WebApplicationFactory<Program> _factory;
    private HttpClient _client;

    [TestInitialize]
    public void Setup()
    {
        _factory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    // 移除原有注册
                    var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IDateTimeProvider));
                    if (descriptor != null)
                    {
                        services.Remove(descriptor);
                    }

                    // 注册模拟时间服务,设置自定义时间
                    var customTime = new DateTime(2024, 5, 20, 14, 30, 0);
                    services.AddSingleton<IDateTimeProvider>(new MockDateTimeProvider(customTime));
                });
            });

        _client = _factory.CreateClient();
    }

    [TestMethod]
    public async Task CreateDeliveryOrder_WhenWithinDeliveryTime_ReturnsValidOrder()
    {
        // 构造请求、调用接口并验证结果
        var request = new CreateOrderCommand
        {
            Type = OrderTypes.Delivery,
            FoodBusinessId = "目标商家ID",
            // 其他必要参数
        };

        // 发送请求并断言响应符合预期
        // ...
    }

    [TestCleanup]
    public void Cleanup()
    {
        _client.Dispose();
        _factory.Dispose();
    }
}

额外优化建议

  • 避免频繁调用DateTime.Parse,提前解析openingTime和closingTime一次,提升性能和可读性。
  • 将时间字符串解析逻辑封装为辅助方法,减少重复代码。

内容的提问来源于stack exchange,提问作者AZZOUNI MOHAMED AMIR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:55:23