繁体   English   中英

使用Moq,是否可以按类型设置模拟表?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM