天天看點

更優雅的在 Xunit 中使用依賴注入

更優雅的在 Xunit 中使用依賴注入

Xunit.DependencyInjection 7.0 釋出了

Intro

上次我們已經介紹過一次大師的

Xunit.DependencyInjection

https://www.cnblogs.com/weihanli/p/xuint-dependency-injection.html ,最近大師完成了 7.0 的重構并且已經正式釋出,已經可以直接安裝使用了

7.0 為我們帶來了更好的程式設計體驗,在 6.x 的版本中,我們的

Startup

需要繼承于

DependencyInjectionTestFramework

而且需要設定一個 assembly attribute,這在 7.0 中都不需要了,下面我們來看看有了哪些變化

Startup 的變化

首先來看大師給出的 diff

-[assembly: TestFramework("Your.Test.Project.Startup", "Your.Test.Project")]

namespace Your.Test.Project
{
-   public class Startup : DependencyInjectionTestFramework
+   public class Startup
    {
-       public Startup(IMessageSink messageSink) : base(messageSink) { }

-       protected void ConfigureServices(IServiceCollection services)
+       public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDependency, DependencyClass>();
        }

-       protected override IHostBuilder CreateHostBuilder() =>
-           base.CreateHostBuilder(assemblyName)
-               .ConfigureServices(ConfigureServices);

-       protected override void Configure(IServiceProvider provider)
+       public void Configure(IServiceProvider provider)
    }
}
           
  1. 移除了

    TestFramework

    assembly attribute
  2. 不再需要繼承于

    DependencyInjectionTestFramework

  3. 也因為上面的不需要繼承,是以原本要

    override

    的方法可以不

    override

    了,原來是

    protected

    的方法現在需要改成

    public

這樣改了之後首先我們在使用的時候無需知道

DependencyInjectionTestFramework

的存在了,而且可以更符合 asp.net core

Startup

的使用習慣,可以屏蔽掉很多實作細節,使用者隻需要在

Startup

注冊自己的邏輯即可,更為專注于自己的邏輯而無需關心架構所做的事情

新的 Startup 解析

我把上一篇文章寫的示例用更新到了新的版本,下面是更新後的示例代碼

namespace XUnitDependencyInjectionSample
{
    public class Startup
    {
        // 自定義 HostBuilder ,可以沒有這個方法,沒有這個方法會使用預設的 hostBuilder,通常直接使用 `ConfigureHost` 應該就夠用了
        // public IHostBuilder CreateHostBuilder()
        // {
        //     return new HostBuilder()
        //         .ConfigureAppConfiguration(builder =>
        //         {
        //             // 注冊配置
        //             builder
        //                 .AddInMemoryCollection(new Dictionary<string, string>()
        //                 {
        //                     {"UserName", "Alice"}
        //                 })
        //                 .AddJsonFile("appsettings.json")
        //                 ;
        //         })
        //         .ConfigureServices((context, services) =>
        //         {
        //             // 注冊自定義服務
        //             services.AddSingleton<IIdGenerator, GuidIdGenerator>();
        //             if (context.Configuration.GetAppSetting<bool>("XxxEnabled"))
        //             {
        //                 services.AddSingleton<IUserIdProvider, EnvironmentUserIdProvider>();
        //             }
        //         })
        //         ;
        // }

        // 自定義 host 建構
        public void ConfigureHost(IHostBuilder hostBuilder)
        {
            hostBuilder
                .ConfigureAppConfiguration(builder =>
                {
                    // 注冊配置
                    builder
                        .AddInMemoryCollection(new Dictionary<string, string>()
                        {
                            {"UserName", "Alice"}
                        })
                        .AddJsonFile("appsettings.json")
                        ;
                })
                .ConfigureServices((context, services) =>
                {
                    // 注冊自定義服務
                    services.AddSingleton<IIdGenerator, GuidIdGenerator>();
                    if (context.Configuration.GetAppSetting<bool>("XxxEnabled"))
                    {
                        services.AddSingleton<IUserIdProvider, EnvironmentUserIdProvider>();
                    }
                })
                ;
        }

        // 支援的形式:
        // ConfigureServices(IServiceCollection services)
        // ConfigureServices(IServiceCollection services, HostBuilderContext hostBuilderContext)
        // ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
        public void ConfigureServices(IServiceCollection services, HostBuilderContext hostBuilderContext)
        {
            services.TryAddSingleton<CustomService>();
        }

        // 可以添加要用到的方法參數,會自動從注冊的服務中擷取服務執行個體,類似于 asp.net core 裡 Configure 方法
        public void Configure(IServiceProvider applicationServices, IIdGenerator idGenerator)
        {
            // 有一些測試資料要初始化可以放在這裡
            // InitData();
        }
    }
}
           

在新的版本中

Startup

和 asp.net core 裡的

Startup

更加相像了,

會多一個

CreateHostBuilder

/

ConfigureHost(IHostBuilder)

的方法,允許使用者自定義 Host 的建構,也可以沒有這個方法

ConfigureServices

方法允許使用者增加

HostBuilderContext

作為參數,可以通過

hostBuilderContext

來擷取配置資訊,也可以在

CreateHostBuilder

ConfigureHost(IHostBuilder)

裡注冊也是一樣的

注冊配置/服務和 asp.net core 裡一模一樣,有資料或配置需要在項目啟動時初始化的,可以放在

Configure

方法做,有點類似于 asp.net core 裡

Startup

中的

Configure

方法,可以将需要的服務作為方法參數,執行時會自動從注冊的服務中擷取

Startup 的尋找方法

預設的

Startup

通常是

ProjectName.Startup

,通常在項目根目錄下建立一個

Startup

是不需要配置的,如果不是或不起作用,可以參考下面 Startup 的尋找規則

如果要使用一個特别的

Startup

, 你可以通過在項目檔案的

PropertyGroup

部分定義

XunitStartupAssembly

XunitStartupFullName

,具體規則如下

<Project>
  <PropertyGroup>
    <XunitStartupAssembly>Abc</XunitStartupAssembly>
    <XunitStartupFullName>Xyz</XunitStartupFullName>
  </PropertyGroup>
</Project>
           
XunitStartupAssembly XunitStartupFullName Startup
Your.Test.Project.Startup, Your.Test.Project
Abc Abc.Startup, Abc
Xyz Xyz, Your.Test.Project
Xyz, Abc

More

除了上面的

Startup

的改動之外,新版本還支援了 xunit 中 fixture 的依賴注入,似乎是由一個外國小哥提的 PR, 詳見:https://github.com/pengweiqhca/Xunit.DependencyInjection/pull/21

有了這個神器,在測試代碼中使用依賴注入要友善很多了,還沒有用起來的可以準備上手了~~

Reference

  • https://github.com/pengweiqhca/Xunit.DependencyInjection
  • https://github.com/WeihanLi/SamplesInPractice/tree/master/XUnitDependencyInjectionSample

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。