使用Moq结合工作单元与泛型仓储做单元测试时如何获取单个对象
问题原因
FirstOrDefault是LINQ提供的扩展方法,Moq 不支持直接在Setup表达式里模拟扩展方法,只能模拟接口/类本身定义的可重写成员,也就是你只能Setup仓储的Get方法本身,不能连后面的FirstOrDefault一起写进Setup的表达式树里。- 参数匹配规则不兼容。Lambda表达式是引用类型,你Setup里写的
store => store.WebStoreId == webStoreId和业务代码运行时动态生成的Lambda不是同一个实例,默认按引用匹配的规则下会匹配失败,导致Mock返回空值。
正确实现方式
方式1:通用匹配(最简单)
只需要Mock仓储的Get方法返回你构造的测试集合即可,后续的FirstOrDefault是真实执行的LINQ逻辑,不需要模拟:
[Test] public void GetWebstore_GetSingle() { var webStoreId = 1; var customerName = "A"; var listWebstore = new List<WebStore>() { new WebStore { WebStoreId = webStoreId, CustomerName = customerName } }; var webstore = new WebStore { WebStoreId = webStoreId, CustomerName = customerName }; // 设置Mock,匹配任意传入Get方法的参数,返回预设的集合 _unitOfWork.Setup(uow => uow.GetRepository<WebStore>() .Get( It.IsAny<Expression<Func<WebStore, bool>>>(), It.IsAny<Func<IQueryable<WebStore>, IOrderedQueryable<WebStore>>>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>() )) .Returns(listWebstore); // 后续执行测试逻辑即可 }
方式2:严格匹配筛选逻辑(更严谨)
如果需要验证业务代码确实是按WebStoreId做筛选,可以对传入的Lambda做逻辑匹配:
[Test] public void GetWebstore_GetSingle() { var webStoreId = 1; var customerName = "A"; var expectedWebstore = new WebStore { WebStoreId = webStoreId, CustomerName = customerName }; var listWebstore = new List<WebStore>() { expectedWebstore }; // 定义筛选条件匹配规则:传入的lambda执行后能匹配我们的测试对象 Func<Expression<Func<WebStore, bool>>, bool> FilterMatcher = filter => filter.Compile().Invoke(expectedWebstore); _unitOfWork.Setup(uow => uow.GetRepository<WebStore>() .Get( It.Is<Expression<Func<WebStore, bool>>>(f => FilterMatcher(f)), It.IsAny<Func<IQueryable<WebStore>, IOrderedQueryable<WebStore>>>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>() )) .Returns(listWebstore); }
额外注意点
你业务代码里的GetAllWebstore方法返回值标注为WebStore,但实际返回的是GetAll()的集合结果,属于笔误,需要修正返回类型为IEnumerable<WebStore>,否则运行时会抛出类型转换异常。
内容的提问来源于stack exchange,提问作者sadko
相关产品推荐
相关产品推荐

