實戰指南:使用 xUnit.DependencyInjection 在單元測試中實現依賴註入【完整教程】

来源:https://www.cnblogs.com/ruipeng/p/18134907
-Advertisement-
Play Games

引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...


引言

上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。

Xunit.DependencyInjection

Xunit.DependencyInjection 是一個用於 xUnit 測試框架的擴展庫,它提供了依賴註入的功能,使得在編寫單元測試時可以更方便地進行依賴註入。通過使用 Xunit.DependencyInjection,可以在 xUnit 測試中使用依賴註入容器(比如 Microsoft.Extensions.DependencyInjection)來管理測試中所需的各種依賴關係,包括服務、日誌、配置等等。

使用

我們用Xunit.DependencyInjection對上一章的Sample.Repository進行單元測試。

Nuget包安裝項目依賴

PM> NuGet\Install-Package Xunit.DependencyInjection -Version 9.1.0

創建測試類

public class StaffRepositoryTest
{
    [Fact]
    public void DependencyInject_WhenCalled_ReturnTrue()
    {
        Assert.True(true);
    }
}

運行測試 先看一下

image

從這可以得出一個結論 如果安裝了Xunit.DependencyInjectionxUnit單元測試項目啟動時會檢測是否有預設的Startup

如果你安裝了Xunit.DependencyInjection但是還沒有準備好在項目中使用也可以在csproj中禁用

<Project>
    <PropertyGroup>
        <EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>false</EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>
    </PropertyGroup>
</Project>

再測試一下

image

可以看到我們添加的配置生效了

配置

在我們的測試項目中新建Startup.cs

public class Startup
{

}

.Net 6 之前我們不就是用這個來配置項目的依賴和管道嗎,其實這個位置也一樣用它來對我們項目的依賴和服務做一些基礎配置,使用配置單元測試的Startup其實和配置我們的Asp.Net Core的啟動配置是一樣的

CreateHostBuilder

CreateHostBuilder 方法用於創建應用程式的主機構建器(HostBuilder)。在這個方法中,您可以配置主機的各種參數、服務、日誌、環境等。這個方法通常用於配置主機構建器的各種屬性,以便在應用程式啟動時使用。

public IHostBuilder CreateHostBuilder([AssemblyName assemblyName]) { }

ConfigureHost

ConfigureHost 方法用於配置主機構建器。在這個方法中,您可以對主機進行一些自定義的配置,比如設置環境、使用特定的配置源等

  public void ConfigureHost(IHostBuilder hostBuilder) { }

ConfigureServices

ConfigureServices 方法用於配置依賴註入容器(ServiceCollection)。在這個方法中,您可以註冊應用程式所需的各種服務、中間件、日誌、資料庫上下文等等。這個方法通常用於配置應用程式的依賴註入服務。

Configure

ConfigureServices 中配置的服務可以在 Configure 方法中指定。如果已經配置的服務在 Configure 方法的參數中可用,它們將會被註入

    public void Configure()
    {

    }

Sample.Repository

接下來對我們的倉儲層進行單元測試
已知我們的倉儲層已經有註入的擴展方法

    public static IServiceCollection AddEFCoreInMemoryAndRepository(this IServiceCollection services)
    {
        services.AddScoped<IStaffRepository, StaffRepository>();
        services.AddDbContext<SampleDbContext>(options => options.UseInMemoryDatabase("sample").EnableSensitiveDataLogging(), ServiceLifetime.Scoped);
        return services;
    }

所以我們只需要在單元測試項目的StartupConfigureServices 註入即可。
對我們的Sample.Repository添加項目引用,然後進行依賴註冊

    public void ConfigureServices(IServiceCollection services, HostBuilderContext context)
    {
        services.AddEFCoreInMemoryAndRepository();
    }

好了接下來編寫單元測試Case

依賴項獲取:

public class StaffRepositoryTest
{
    private readonly IStaffRepository _staffRepository;
    public StaffRepositoryTest(IStaffRepository staffRepository)
    {
        _staffRepository = staffRepository;
    }
}

在測試類中使用依賴註入和我們正常獲取依賴是一樣的都是通過構造函數的形式

 public class StaffRepositoryTest
{
    private readonly IStaffRepository _staffRepository;
    public StaffRepositoryTest(IStaffRepository staffRepository)
    {
        _staffRepository = staffRepository;
    }

    //[Fact]
    //public void DependencyInject_WhenCalled_ReturnTrue()
    //{
    //    Assert.True(true);
    //}

    [Fact]
    public async Task AddStaffAsync_WhenCalled_ShouldAddStaffToDatabase()
    {
        // Arrange
        var staff = new Staff { Name = "zhangsan", Email = "[email protected]" };
        // Act
        await _staffRepository.AddStaffAsync(staff, CancellationToken.None);
        // Assert
        var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None);
        Assert.NotNull(retrievedStaff); // 確保 Staff 已成功添加到資料庫
        Assert.Equal("zhangsan", retrievedStaff.Name); // 檢查名稱是否正確
    }


    [Fact]
    public async Task DeleteStaffAsync_WhenCalled_ShouldDeleteStaffFromDatabase()
    {

        var staff = new Staff { Name = "John", Email = "[email protected]" };
        await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一個 Staff

        // Act
        await _staffRepository.DeleteStaffAsync(staff.Id, CancellationToken.None); // 刪除該 Staff

        // Assert
        var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 嘗試獲取已刪除的 Staff
        Assert.Null(retrievedStaff); // 確保已經刪除

    }


    [Fact]
    public async Task UpdateStaffAsync_WhenCalled_ShouldUpdateStaffInDatabase()
    {
        // Arrange
        var staff = new Staff { Name = "John", Email = "[email protected]" };
        await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一個 Staff

        // Act
        staff.Name = "Updated Name";
        await _staffRepository.UpdateStaffAsync(staff, CancellationToken.None); // 更新 Staff

        // Assert
        var updatedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 獲取已更新的 Staff
        Assert.Equal("Updated Name", updatedStaff?.Name); // 確保 Staff 已更新

    }

    [Fact]
    public async Task GetStaffByIdAsync_WhenCalledWithValidId_ShouldReturnStaffFromDatabase()
    {
        // Arrange
        var staff = new Staff { Name = "John", Email = "[email protected]" };
        await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一個 Staff
                                                                             // Act
        var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 獲取 Staff
                                                                                                         // Assert
        Assert.NotNull(retrievedStaff); // 確保成功獲取 Staff

    }

    [Fact]
    public async Task GetAllStaffAsync_WhenCalled_ShouldReturnAllStaffFromDatabase()
    {
        // Arrange
        var staff1 = new Staff { Name = "John", Email = "[email protected]" };
        var staff2 = new Staff { Name = "Alice", Email = "[email protected]" };
        await _staffRepository.AddStaffAsync(staff1, CancellationToken.None); // 先添加 Staff1
        await _staffRepository.AddStaffAsync(staff2, CancellationToken.None); // 再添加 Staff2

        // Act
        var allStaff = await _staffRepository.GetAllStaffAsync(CancellationToken.None); // 獲取所有 Staff

        // Assert
        List<Staff> addStaffs = [staff1, staff2];
        Assert.True(addStaffs.All(_ => allStaff.Any(x => x.Id == _.Id))); // 確保成功獲取所有 Staff
    }
}

Run Tests

image

可以看到單元測試已經都成功了,是不是很簡單呢。

擴展

如何註入 ITestOutputHelper?

之前的示例不使用xUnit.DependencyInjection我們用ITestOutputHelper通過構造函數構造,現在是用ITestOutputHelperAccessor

public class DependencyInjectionTest
{
    private readonly ITestOutputHelperAccessor _testOutputHelperAccessor;
    public DependencyInjectionTest(ITestOutputHelperAccessor testOutputHelperAccessor)
    {
        _testOutputHelperAccessor = testOutputHelperAccessor;
    }

    [Fact]
    public void TestOutPut_Console()
    {
        _testOutputHelperAccessor.Output?.WriteLine("測試ITestOutputHelperAccessor");
        Assert.True(true);
    }
}

OutPut:

image

日誌輸出到 ITestOutputHelper

Nuget安裝

PM> NuGet\Install-Package Xunit.DependencyInjection.Logging -Version 9.0.0

ConfigureServices配置依賴

 public void ConfigureServices(IServiceCollection services)
        => services.AddLogging(lb => lb.AddXunitOutput());

使用:

public class DependencyInjectionTest
{
    private readonly ILogger<DependencyInjectionTest> _logger;
    public DependencyInjectionTest(ILogger<DependencyInjectionTest> logger)
    {
        _logger = logger;
    }

    [Fact]
    public void Test()
    {
        _logger.LogDebug("LogDebug");
        _logger.LogInformation("LogInformation");
        _logger.LogError("LogError");
    }
}

OutPut:

 標準輸出: 
[2024-04-12 16:00:24Z] info: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
      LogInformation
[2024-04-12 16:00:24Z] fail: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
      LogError

startup 類中註入 IConfiguration 或 IHostEnvironment

通過ConfigureServices設置 EnvironmentName和使用IConfiguration

   public void ConfigureServices(HostBuilderContext context)
    {
        context.HostingEnvironment.EnvironmentName = "test";
           //使用配置
        context.Configuration.GetChildren();
    }

也可以使用Startup下的ConfigureHost設置

public class Startup
{
    public void ConfigureHost(IHostBuilder hostBuilder) =>
        hostBuilder
            .ConfigureServices((context, services) => { context.XXXX });
}

在 ConfigureHost 下可以對.Net IHostBuilder進行配置,可以對IConfiguration,IServiceCollection,Log等跟Asp.Net Core使用一致。

集成測試

xUnit.DependencyInjection 也可以對Asp.Net Core項目進行集成測試

安裝 Microsoft.AspNetCore.TestHost

PM> NuGet\Install-Package Microsoft.AspNetCore.TestHost -Version 9.0.0-preview.3.24172.13
    public void ConfigureHost(IHostBuilder hostBuilder) =>
        hostBuilder.ConfigureWebHost[Defaults](webHostBuilder => webHostBuilder
            .UseTestServer(options => options.PreserveExecutionContext = true)
            .UseStartup<AspNetCoreStartup>());

可以參考 xUnit 的官網實現,其實有更優雅的實現集成測試的方案,xUnit.DependencyInject 的集成測試方案僅做參考集合,在後面章節筆者會對集成測試做詳細的介紹。

最後

希望本文對您在使用 Xunit.DependencyInjection 進行依賴註入和編寫單元測試時有所幫助。通過本文的介紹,您可以更加靈活地管理測試項目中的依賴關係,提高測試代碼的可維護性和可測試性


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 自從ChatGPT火了之後,各種產品都在不停的擁抱AI,在各自場景中接入AI,國內外各種大模型層出不窮。 好像有點扯遠了,言歸正傳,今天我們要說的是SpringAI,大家在逛Spring 官網(https://spring.io/) 應該發現了,在官網中多了SpringAI 模塊 一、Sp ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...