實戰指南:使用 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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...