NSubstitute单元测试失败:未检测到AppContext预期调用
问题分析与修复方案
核心问题1:断言方法与实际调用不匹配
你的MyService.AddProduct内部调用的是异步方法AddAsync和SaveChangesAsync,但测试代码里验证的是同步方法Add和SaveChanges,这直接导致断言失败——实际根本没调用同步方法。
核心问题2:可能存在DbSet模拟缺失(若服务操作的是DbSet而非直接调用DbContext方法)
如果AddProduct实际是操作AppContext中的DbSet<Product>(比如await _appContext.Products.AddAsync(mappedEntity)),而非直接调用_appContext.AddAsync,仅模拟IAppContext是不够的,还需模拟对应的Products DbSet,否则服务调用时会因DbSet为null无法触发预期方法调用。
修复步骤
步骤1:修正断言,匹配异步调用
把测试中的断言改为验证实际调用的异步方法:
// Assert await appContext.Received(1).AddAsync(Arg.Any<Product>()); await appContext.Received(1).SaveChangesAsync();
步骤2:确保IAppContext接口包含所需异步方法
如果IAppContext是自定义接口,必须确保它包含AddAsync和SaveChangesAsync的定义,否则NSubstitute模拟的接口不会有这些方法,服务实际调用的是基类DbContext的方法,而非接口方法,导致断言失效:
public interface IAppContext : IDisposable { Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); Task<EntityEntry<TEntity>> AddAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class; // 若操作DbSet,需定义对应属性 DbSet<Product> Products { get; set; } }
步骤3:若服务操作DbSet,需模拟DbSet并注入上下文
如果AddProduct代码是操作DbSet<Product>,比如:
public async Task AddProduct(ProductDTO dto) { var product = MapToEntity(dto); await _appContext.Products.AddAsync(product); await _appContext.SaveChangesAsync(); }
则需先模拟DbSet<Product>,再赋值给IAppContext的Products属性:
// Arrange var mockProductsDbSet = Substitute.For<DbSet<Product>, IQueryable<Product>>(); var appContext = Substitute.For<IAppContext>(); appContext.Products.Returns(mockProductsDbSet); var myService = new MyService(appContext); // ... 其他初始化代码 // Act await myService.AddProduct(productDTO); // Assert await mockProductsDbSet.Received(1).AddAsync(Arg.Any<Product>()); await appContext.Received(1).SaveChangesAsync();
步骤4:验证实体映射(可选排查点)
若上述修复后仍失败,检查ProductDTO到Product的映射是否正确,确保映射后的实体有效,没有因映射问题导致服务内部跳过AddAsync调用。
内容的提问来源于stack exchange,提问作者hillcountry99
相关产品推荐
相关产品推荐

