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

如何使用NSubstitute为调用ASP.NET Core IMemoryCache的方法配置单元测试Mock

问题根因

GetOrCreateAsync是IMemoryCache的扩展方法,不属于接口原生定义,NSubstitute无法直接Mock扩展方法,需要对扩展方法底层调用的IMemoryCache原生方法、以及依赖的ICacheEntry实例进行完整配置,才不会出现返回值类型不匹配、CacheEntry为null的报错。

正确Mock配置步骤

1. 模拟ICacheEntry实例

先构造符合要求的ICacheEntry模拟对象,覆盖所有缓存操作会用到的成员:

var mockCacheEntry = Substitute.For<ICacheEntry>();
// 配置Key属性
mockCacheEntry.Key.Returns("对应缓存key");
// 配置绝对过期设置所需属性
mockCacheEntry.AbsoluteExpirationRelativeToNow.Returns(TimeSpan.FromMinutes(5));
// 配置Dispose方法无异常
mockCacheEntry.When(x => x.Dispose()).Do(_ => { });

2. 配置IMemoryCache核心原生方法

GetOrCreateAsync底层会依次调用TryGetValue、CreateEntry,按需选择配置方案即可:

方案A:直接返回缓存值,跳过缓存工厂逻辑(适合不需要测试缓存工厂的场景)

var mockMemoryCache = Substitute.For<IMemoryCache>();
// 配置缓存命中,直接返回预设的DataEntity
object expectedData = new DataEntity { Id = "id" };
mockMemoryCache.TryGetValue(Arg.Any<object>(), out expectedData).Returns(true);

方案B:允许走缓存工厂逻辑(适合需要测试缓存工厂内部逻辑的场景)

var mockMemoryCache = Substitute.For<IMemoryCache>();
// 配置缓存未命中,走工厂逻辑
object cacheValue = null;
mockMemoryCache.TryGetValue(Arg.Any<object>(), out cacheValue).Returns(false);
// 配置CreateEntry返回我们模拟的ICacheEntry
mockMemoryCache.CreateEntry(Arg.Any<object>()).Returns(mockCacheEntry);

3. 修正测试代码的其他问题

现有测试代码还存在以下错误需要同步修正:

  • 业务方法中调用的是GetInfo而非GetUser,不需要配置service.GetUser的返回,需要对应配置GetInfo的依赖,或是直接MockGetInfo的返回值
  • Act阶段调用UpdateWeight的代码少了右括号
  • 业务方法中未定义newWeight变量,需要对齐参数名
完整可运行测试示例
public async Task UpdateWeight_WhenDataExists_ReturnSuccessResult()
{
    // Arrange
    var id = "id";
    int inputWeight = 30;
    var mockData = new DataEntity { Id = id, UserId = "user1" };
    var mockUser = new User { UserId = "user1" };

    // 配置缓存Mock
    var mockCacheEntry = Substitute.For<ICacheEntry>();
    mockCacheEntry.When(x => x.Dispose()).Do(_ => { });
    object cacheVal = null;
    var mockMemoryCache = Substitute.For<IMemoryCache>();
    mockMemoryCache.TryGetValue(Arg.Any<object>(), out cacheVal).Returns(false);
    mockMemoryCache.CreateEntry(Arg.Any<object>()).Returns(mockCacheEntry);

    // 注入模拟缓存到你的服务实例
    var yourService = new YourService(mockMemoryCache /* 其他依赖参数 */);
    // 配置服务内部依赖的方法返回
    yourService.GetDataFromDb(Arg.Any<string>()).Returns(mockData);
    yourService.GetInfo(Arg.Any<string>()).Returns(mockUser);
    yourService.UpdateWeight(Arg.Any<int>(), Arg.Any<User>()).Returns(true);

    // Act
    var result = await yourService.UpdateWeight(id, inputWeight);

    // Assert
    result.IsSuccessful.Should().BeTrue();
    result.Weight.Should().Be(inputWeight);
}

内容的提问来源于stack exchange,提问作者Noelia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 02:36:05