簡體   English   中英

單元測試包含 Using 塊的方法

[英]Unit Testing a method which contains a Using block

我已經寫了(幾年前寫的)C# function,我被要求用單元測試來覆蓋這個方法。

public string PlaceOrder(int requestId, string orderedby)
    {
        try
        {
            using (DatabaseContext dbContext = new DatabaseContext("myConnectionStringHere"))
            {
                var req = dbContext.Orders.Where(row => row.id == requestId).FirstOrDefault();
                if (req == null)
                    return "not found";
               
                req.status="A";
                dbContext.SaveChanges();
                return "found";
            }
        }
        catch (Exception ex)
        {
            return "error";
        }
    }

現在,在進行單元測試時,我需要確保它不會向數據庫寫入任何內容,因此我必須對其進行MOQ 我怎么能MOQ ,它包含Using塊。

我知道架構本來可以更好,應該遵循設計模式,但我不允許更改應用程序的結構,因為它是遺留應用程序。

這里的一般指導是更喜歡在 memory(w/o sqlite)數據庫中進行集成測試而不是單元測試。

讓我向您推薦四個幫助庫,它們可以使您的測試更容易:

EntityFrameworkCoreMock

Github 鏈接

這里的先決條件是將您的DbSet標記為virtual的,如下所示:

public virtual DbSet<Order> Orders { get; set; }

然后你可以創建一個模擬,你可以用一些虛擬數據填充你的Orders集合:

var initialOrders = new[]
{
    new Order { ... },
    new Order { ... },
};

var dbContextMock = new DbContextMock<DatabaseContext>(new DbContextOptionsBuilder<DatabaseContext>().Options);
var ordersDbSetMock = dbContextMock.CreateDbSetMock(db => db.Orders, initialOrders);

您必須重寫包含 class 的PlaceOrder方法,以便在構造函數中接收DatabaseContext參數,以便能夠在測試期間注入dbContextMock.Object

在斷言階段,您可以查詢數據並對數據進行斷言。 由於您不調用AddRemove或任何其他 CRUD 方法,因此您只能Verify SaveChanges調用。

public void GivenAnExistingOrder_WhenICallPlaceOrder_ThenSaveChangesIsCalledOnce()
{
   ...
   //Assert
   dbMock.Verify(db => db.SaveChanges(), Times.Once);
}

public void GivenANonExistingOrder_WhenICallPlaceOrder_ThenSaveChangesIsCalledNever()
{
   ...
   //Assert
   dbMock.Verify(db => db.SaveChanges(), Times.Never);
}

EntityFrameworkCore.Testing

Github 鏈接

它的工作方式或多或少與以前的庫相同。

var dbContextMock = Create.MockedDbContextFor<DatabaseContext>();
dbContextMock.Set<Order>().AddRange(initialOrders);
dbContextMock.SaveChanges();

斷言以相同的方式工作。


第三個(不太成熟的)庫稱為Moq.EntityFrameworkCore


如果您真的熱衷於通過避免在 memory 數據庫中執行單元測試,那么您應該嘗試使用MockQueryable庫。

const int requestId = 1;
var orders = new List<Order>();
var ordersMock = orders.AsQueryable().BuildMockDbSet();
ordersMock.Setup(table => table.Where(row => row.Id == requestId)).Returns(...)

在這里,您基本上是 mocking 應該是Where過濾器的結果。 為了能夠使用它,包含 class 的PlaceOrder應該通過其構造函數接收DbSet<Order>參數。

或者,如果您有一個IDatabaseContext接口,那么您也可以像這樣使用它:

Mock<IQueryable<Order>> ordersMock = orders.AsQueryable().Build();
Mock<IDatabaseContext> dbContextMock = ...
dbContextMock.Setup(m => m.ReadSet<Order>()).Returns(ordersMock.Object));

這里應該改變很多東西:

1:

不要直接在代碼庫中以這種方式實現連接字符串。 相反,將您的數據庫直接注入到您的類中。

所以這個偽代碼應該有助於理解一般的想法。

public void ConfigureService(IServiceCollection serviceCollection)
{
   ...

  
   string connectionString = //secure storage;
   serviceCollection.AddDbContext<DatabaseContext>(options => {

      options.UseSqlServer(connectionString);

   });

   ...
}

然后

public class OrderRepository
{
        
   private IServiceScopeFactory _serviceScopeFactory ;
        
   public OrderRepository(IServiceScopeFactory serviceScopeFactory ){
      _serviceScopeFactory = serviceScopeFactory ;
   }
   
   ...
        
        
   public string PlaceOrder(int requestId, string orderedby)
   {
      try
      {
         using (var context = serviceScopeFactory.CreateScope())
         {
            var req = context.Orders.Where(row => row.id == requestId).FirstOrDefault();
            if (req == null)
               return "not found";
                       
            req.status="A";
            context.SaveChanges();
            return "found";
         }
      }
      catch (Exception ex)
      {
         return "error";
      }
   }
    
   ...

}

如果您想進行集成測試,則可以使用 InMemory 數據庫來模擬您想要的任何東西。 或者你可以連接到一個“真正的”數據庫,然后這樣做。

如果你想讓它成為一個單元測試,你可以看到這個鏈接: How to setup a DbContext Mock

2:

為正在下的訂單返回一個字符串,表示找到/未找到,這似乎非常適得其反。

如果您的目標是記錄此信息,請提供一個可以記錄此信息的 DI 記錄器。 (嘗試導入 ILogger 接口,它是微軟對日志記錄的擴展,記不住 nuget package 名稱)應該可以使您非常有效地使用 DI 進行日志記錄。

如果您的目標是讓可能的 UI 顯示此消息,則消息內容不可能源自后端或域邏輯。

至少不是這樣的。

然后你應該為響應創建一個接口,並返回所述接口的實現,該接口至少存在於其他地方,但即使這樣也有點像尿褲子。 (並且包含一個 UI 友好的消息,可以包含一個可能的堆棧跟蹤/異常),以及其他可能的相關信息,比如您嘗試下訂單的 ID 等。)

你應該讓它發生在你的 UI 和域邏輯之間的接口上,前提是字符串的目的是什么。 您希望看到錯誤處理的地方。

3:WTF 趕上了? 你只是返回錯誤? 出色地? 什么錯誤? 你以這種方式丟失了堆棧跟蹤? 有人應該為此受到懲罰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM