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

时间:2017-07-19 10:01:09

标签: c# entity-framework moq

我有以下代码,我正在尝试根据传递给MockDbSet方法的数据类型设置模拟表。

    <script type="text/javascript" src="https://cdn.omise.co/card.js"


      data-key="YOUR_PUBLIC_KEY"
      data-image="PATH_TO_LOGO_IMAGE"
      data-frame-label="Merchant site name"
      data-button-label="Pay now"
      data-submit-label="Submit"
      data-location="yes"
      data-amount="10025"
      data-currency="thb"

      />

我在mockContext.Setup行(22)遇到以下错误:

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);
}

我试过

System.NotSupportedException: Conversion between generic and non-generic DbSet objects is not supported for test doubles.

这不会抛出异常,但也不会设置任何数据。

是否可以这样设置表格?

由于

2 个答案:

答案 0 :(得分:1)

概述存储库级别的模拟:

给定通过合同接口与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);
}

存储库方法将包含最少甚至没有业务逻辑。在我的情况下,存储库只能与:

协调一致
  • 授权(根据当前用户/租户检索数据)
  • 活动/软删除状态。 (除非另有说明,否则检索&#34;活动&#34;软删除环境中的数据。)
  • 时间状态。 (检索&#34;当前&#34;日期,除非另有说明。)

所有业务逻辑都应驻留在您的服务类或控制器中,可以单独进行测试。为了测试上述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风格,或者如果你返回一个实体,只需返回&#34;存根&#34;使用存根顺序并将其返回。

我使用的模拟框架是Moq。仅基于内存,上述代码可能在语法上不完全正确。 :)

就TDD / BDD而言,单元测试的目标是,这些测试应该可靠地重复,并且执行起来快,以便在开发过程中可以反复和频繁地运行。保持存储库相对较薄,而不涉及业务逻辑决策意味着它们可以作为单元测试模拟的可靠截止点。存储库的工作是返回数据,因此通过模拟这一点意味着我们可以控制我们希望被测代码能够使用的数据。我们可以模拟它返回对象,返回null,抛出异常,无论我们的测试场景是否需要我们的测试代码来处理。

在上面的例子中,我还演示了使用包装DB Context的基本工作单元模式。我用于EF的实现是Medhime的DB Context Scope Factory / Locator。使用工作单元模式,我们也有模拟,可以验证被测代码是否(或不是)保存数据。存储库需要有一个工作单元的链接(在构造函数中初始化,或者#34;位于&#34;按照Mehdime模式)但是在测试我们的服务时我们并不关心这个方面&安培;控制器,存储库仅被模拟,其目的是返回和(可选)创建数据。

我将我的存储库作为实体的工厂(即具有产品详细信息和数量列表的CreateOrder()),以确保使用所有预期的参考链接和所需数据初始化新实体,而不是依赖于调用码。那个调用代码必须充满额外的查询等来检索新订单的引用数据,所以我把它传递给视图模型数据到Order Repository以解析,连接并返回一个有效的新订单实体。

答案 1 :(得分:1)

在最近的一个项目中,我创建了一个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);
相关问题