如何在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
相关产品推荐
相关产品推荐

