如何结合Ardalis Specification与NSubstitute按ID匹配仓储模拟?
解决Ardalis Specification与NSubstitute按ID匹配的问题
方案一:给Specification添加公共只读ID属性(推荐)
首先修改ProductByIdSpec,将构造函数传入的ID暴露为只读公共属性,既不破坏封装性,又能方便测试匹配:
public sealed class ProductByIdSpec : SingleResultSpecification<Product> { // 新增只读公共属性,暴露传入的ID public int Id { get; } public ProductByIdSpec(int id) { Id = id; Query.Where(p => p.Id == id); } }
之后在单元测试中,使用Arg.Is的Lambda重载,直接检查Specification的Id属性值:
// 设置模拟仓储的返回规则:当传入Id为42的ProductByIdSpec时,返回对应Product _productRepository .SingleOrDefaultAsync(Arg.Is<ProductByIdSpec>(spec => spec.Id == 42)) .ReturnsAsync(new Product { Id = 42, Name = "测试产品" }); // 调用测试代码 var result = await _productRepository.SingleOrDefaultAsync(new ProductByIdSpec(42)); // result会返回预设的Product实例
方案二:重写Specification的Equals和GetHashCode方法
如果不想暴露公共属性,可以通过重写Equals和GetHashCode,让两个ProductByIdSpec实例只要ID相同就判定为相等:
public sealed class ProductByIdSpec : SingleResultSpecification<Product> { private readonly int _id; public ProductByIdSpec(int id) { _id = id; Query.Where(p => p.Id == id); } public override bool Equals(object obj) { // 仅当obj是ProductByIdSpec且ID相同时,判定为相等 return obj is ProductByIdSpec otherSpec && otherSpec._id == _id; } public override int GetHashCode() { // 基于ID生成哈希码 return HashCode.Combine(_id); } }
此时测试中直接使用Arg.Is(new ProductByIdSpec(42))就能匹配:
_productRepository .SingleOrDefaultAsync(Arg.Is(new ProductByIdSpec(42))) .ReturnsAsync(new Product { Id = 42 });
方案三:通过反射匹配(不推荐)
如果无法修改ProductByIdSpec的代码,可以用反射读取私有字段的ID值,但这种方式依赖编译器生成的字段名,稳定性差,仅作为临时方案:
using System.Reflection; // 设置模拟返回值 _productRepository .SingleOrDefaultAsync(Arg.Is<ProductByIdSpec>(spec => { // 通过反射获取构造函数传入的私有ID字段值 var idField = spec.GetType().GetField("<Id>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); return idField != null && (int)idField.GetValue(spec) == 42; })) .ReturnsAsync(new Product { Id = 42 });
内容的提问来源于stack exchange,提问作者Mathias F
相关产品推荐
相关产品推荐

