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

如何对依赖封装HybridCache的缓存服务的CarFactory进行单元测试(验证缓存未命中时仓储方法的调用)

如何对依赖封装HybridCache的缓存服务的CarFactory进行单元测试(验证缓存未命中时仓储方法的调用)

我完全懂你的需求——就是要确认当缓存里没有对应数据时,CarFactory确实会去调用仓储的GetCarDetailsAsync方法。你之前尝试用Callback来触发传入的委托,但遇到了返回null的问题,这大概率是因为异步回调的处理方式不对,或者mock的设置顺序/细节没做好。下面我给你一步步拆解正确的测试方案:

核心思路

我们需要让mock的ICachingService在调用GetOrCreateAsync时,真正执行传入的获取数据的委托(也就是CarFactory里那个调用仓储的lambda),这样就能触发仓储方法的调用,进而验证它是否被执行。

具体实现步骤(以Moq为例)

  1. 提前设置仓储的Mock返回值
    首先得确保仓储的mock已经配置好,当调用GetCarDetailsAsync时能返回预期的测试数据,不然就算触发了委托,也会因为仓储没返回值而得到null。

  2. 正确配置缓存服务的Mock
    不要用Callback来处理异步逻辑(Moq的Callback对异步void的支持不太友好,容易出现隐式的问题),而是用ReturnsAsync直接执行传入的委托并返回结果,这样既能触发仓储调用,又能正确处理异步流程。

  3. 执行测试并验证仓储调用
    调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:03:04