簡體   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