[英]Using Moq, is it possible to setup mocked tables by type?
我有以下代码,试图根据传递给MockDbSet方法的数据类型来设置模拟表。
private Mock<DbContext> mockContext = new Mock<DbContext>();
public DbContext GetContext()
{
return mockContext.Object;
}
public void MockDbSet<T>(params T[] sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
mockContext.Setup(c => c.Set(typeof(T))).Returns(dbSet.Object);
}
我在mockContext.Setup行(22)处收到以下错误:
System.NotSupportedException: Conversion between generic and non-generic DbSet objects is not supported for test doubles.
我试过了
mockContext.Setup(c => c.Set<T>()).Returns(dbSet.Object);
这不会引发异常,但是也不会设置任何数据。
是否可以通过这种方式设置表格?
谢谢
要在存储库级别概述模拟:
通过合同接口与Repostory交互的给定服务/控制器代码:
public interface IOrderRepository
{
IQueryable<Order> GetOrderById (int orderId);
IQueryable<Order> GetOrdersForCustomer (int customerId);
}
这是我使用的首选存储库模式。 返回IQueryable意味着我的使用者可以利用延迟执行来决定如何使用细节,从而使查询效率更高。 (即使用.Select()获取他们想要的字段,执行.Count()或.Any()、. FirstOrDefault()或.Skip()。Take()等)
或者,您可以使用通用存储库:
public interface IRepository<Order>
{
Order GetOrderById (int orderId);
ICollection<Order> GetOrdersForCustomer (int customerId);
}
存储库方法将包含最少或没有业务逻辑。 在我的情况下,存储库只能与以下内容配合使用:
所有业务逻辑都应驻留在您的服务类或控制器中,可以在其中对其进行单独测试。 为了测试上述3个条件(如果适用),我使用集成测试。 这些条件是非常低级的检查和对任何定期不会改变。
假设被测代码在Controller中。
public class OrderController : IOrderController
{
private readonly IOrderRepository _repository = null;
private readonly IUnitOfWorkFactory _uowFactory = null;
public OrderController(IUnitOfWorkFactory uowFactory, IOrderRepository repository)
{
if (uowFactory == null)
throw new ArgumentNullException("uowFactory");
if (repository == null)
throw new ArgumentNullException("repository");
_uowFactory = uowFactory;
_repository = repository;
}
public void SomeActionOnOrder(int orderId)
{
using (var unitOfWork = _uowFactory.Create())
{
var order = _repository.GetOrderById(orderId)
// Here lies your validation checks that the order was found,
// business logic to do your behaviour.. This is the stuff you want to test..
// ...
unitOfWork.Commit();
}
}
}
现在,当您去测试控制器时...
[Test]
public void EnsureSomeActionOnOrderDoesIt()
{
var uowMock = new Mock<IUnitOfWork>();
var uowFactoryMock = new Mock<IUnitOfWorkFactory>();
var repositoryMock = new Mock<IOrderRepository>();
var testOrderId = -1;
var stubOrders = new [] { newOrder { /* populate expected data... */ } };
uowMock.Setup(x=>x.Commit());
uowFactoryMock.Setup(x=>x.Create()).Returns(uowMock.Object);
repositoryMock.Setup(x=>x.GetOrderById(testOrderId)).Returns(stubOrders.AsQueryable());
var testController = new OrderController(uowFactoryMock.Object, repositoryMock.Object);
testController.SomeActionOnOrder(testOrderId);
// Everything "touched" as expected? (did the code fetch the object? did it save the changes?)
uowFactoryMock.VerifyAll();
uowMock.VerifyAll();
repositoryMock.VerifyAll();
// Perform asserts on your stub order if SomeAction modified state as you expected.
}
针对实际数据库的集成测试将处理存储库应涵盖的任何逻辑。
我上面的存储库模式是IQueryable风格,或者,如果您返回一个实体,只需返回带有存根顺序的“存根”并返回它。
我使用的模拟框架是Moq。 仅基于内存,以上代码在语法上可能并不完全正确。 :)
直到TDD / BDD为止,单元测试的目标是这些测试应该可靠地可重复且快速执行,以便在开发时可以重复且频繁地运行它们。 保持存储库相对较薄,并且不涉及业务逻辑决策,这意味着它们可以充当单元测试模拟的可靠起点。 存储库的工作是返回数据,因此通过进行模拟,这意味着我们可以控制我们希望被测代码可以使用的数据。 我们可以模拟它以返回对象,null,引发异常,无论我们的测试方案希望我们的被测试代码处理什么。
在上面的示例中,我还演示了包装数据库上下文的基本工作单元模式的使用。 我用于EF的实现是Medhime的DB Context Scope Factory / Locator。 使用工作单元模式,我们还提供了一些模拟,可以验证被测代码是否正在(或不)保存数据。 存储库需要链接到一个工作单元(在构造函数中初始化,或者按照Mehdime模式“定位”),但是在测试我们的服务和控制器时我们并不关心该方面,存储库仅仅是模拟出来,其目的是返回和(可选)创建数据。
我将把我的存储库用作实体的工厂(即带有产品详细信息和数量列表的CreateOrder()),以确保使用所有预期的参考链接和所需数据初始化新实体,而不是依赖于调用代码。 该调用代码将必须充斥额外的查询等,以检索新订单的参考数据,因此,我将视图模型数据传递到订单存储库以进行解析,连接并返回有效的新订单。实体。
在最近的项目中,我创建了List<T>
的扩展方法(可以是IEnumerable或其他任何方法)。
public static Mock<DbSet<T>> MockList<T>(this List<T> list) where T: class
{
var mockDbSet = new Mock<DbSet<T>>();
var queryable = list.AsQueryable();
mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
return mockDbSet;
}
调用起来非常简单。
var myData = new List<MyDataType> { new MyDataType(), new MyDataType(), ....};
var mockDb = new Mock<MyContext>();
mockDb.Setup(x => x.MyDatas).Returns(myData.MockList().Object);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.