如何对依赖封装HybridCache的缓存服务的CarFactory进行单元测试(验证缓存未命中时仓储方法的调用)
我完全懂你的需求——就是要确认当缓存里没有对应数据时,CarFactory确实会去调用仓储的GetCarDetailsAsync方法。你之前尝试用Callback来触发传入的委托,但遇到了返回null的问题,这大概率是因为异步回调的处理方式不对,或者mock的设置顺序/细节没做好。下面我给你一步步拆解正确的测试方案:
核心思路
我们需要让mock的ICachingService在调用GetOrCreateAsync时,真正执行传入的获取数据的委托(也就是CarFactory里那个调用仓储的lambda),这样就能触发仓储方法的调用,进而验证它是否被执行。
具体实现步骤(以Moq为例)
提前设置仓储的Mock返回值
首先得确保仓储的mock已经配置好,当调用GetCarDetailsAsync时能返回预期的测试数据,不然就算触发了委托,也会因为仓储没返回值而得到null。正确配置缓存服务的Mock
不要用Callback来处理异步逻辑(Moq的Callback对异步void的支持不太友好,容易出现隐式的问题),而是用ReturnsAsync直接执行传入的委托并返回结果,这样既能触发仓储调用,又能正确处理异步流程。执行测试并验证仓储调用
调用CarFactory的方法后,用Moq的Verify方法确认仓储的方法确实被调用了一次。
完整测试代码示例
using Moq; using Xunit; // 或者你使用的其他测试框架 public class CarFactoryTests { [Fact] public async Task GetCarDetailsAsync_WhenCacheMiss_CallsCarRepo() { // 1. 准备测试数据和Mock对象 var testCarId = Guid.NewGuid(); var testCarDetails = new CarDetails { Id = testCarId, // 填充其他你需要的测试字段 }; var carRepoMock = new Mock<ICarDetailsRepository>(); var cachingServiceMock = new Mock<ICachingService>(); // 2. 配置仓储Mock:调用指定carId时返回测试数据 carRepoMock .Setup(repo => repo.GetCarDetailsAsync(testCarId, It.IsAny<CancellationToken>())) .ReturnsAsync(testCarDetails); // 3. 配置缓存服务Mock:触发传入的获取数据委托 cachingServiceMock .Setup(csm => csm.GetOrCreateAsync<CarDetails>( testCarId, It.IsAny<string>(), It.IsAny<Func<CancellationToken, ValueTask<CarDetails>>>())) .ReturnsAsync((Guid id, string key, Func<CancellationToken, ValueTask<CarDetails>> func) => func.Invoke(default)); // 执行传入的委托,触发仓储调用 // 4. 实例化待测试的CarFactory var carFactory = new CarFactory(carRepoMock.Object, cachingServiceMock.Object); // 5. 执行测试方法 var result = await carFactory.GetCarDetailsAsync(testCarId); // 6. 验证结果和仓储调用 Assert.NotNull(result); Assert.Equal(testCarId, result.Id); // 确认仓储的GetCarDetailsAsync被调用了一次 carRepoMock.Verify( repo => repo.GetCarDetailsAsync(testCarId, It.IsAny<CancellationToken>()), Times.Once, "缓存未命中时,仓储方法应该被调用一次"); } }
为什么你之前的代码会出问题?
你用Callback时写了async (id, key, func) => await func.Invoke(default),但Moq的Callback不支持异步返回值(本质是async void),这会导致异步流程没有被正确等待,可能在仓储的mock还没生效时就已经执行完了,所以得到null。而用ReturnsAsync则能正确处理异步委托的执行和返回,保证整个流程的同步性。
额外补充:测试缓存命中的场景
如果想测试缓存命中时仓储不会被调用,只需要修改缓存服务的mock,让它直接返回测试数据即可:
cachingServiceMock .Setup(csm => csm.GetOrCreateAsync<CarDetails>( testCarId, It.IsAny<string>(), It.IsAny<Func<CancellationToken, ValueTask<CarDetails>>>())) .ReturnsAsync(testCarDetails);
此时验证仓储的Verify应该用Times.Never。
内容来源于stack exchange

