XUnit 依賴注入

WeihanLi發表於2018-10-08

XUnit 依賴注入

Intro

現在的開發中越來越看重依賴注入的思想,微軟的 Asp.Net Core 框架更是天然整合了依賴注入,那麼在單元測試中如何使用依賴注入呢?

本文主要介紹如何通過 XUnit 來實現依賴注入, XUnit 主要藉助 SharedContext 來共享一部分資源包括這些資源的建立以及釋放。

Scoped

針對 Scoped 的物件可以藉助 XUnit 中的 IClassFixture 來實現

  1. 定義自己的 Fixture,需要初始化的資源在構造方法裡初始化,如果需要在測試結束的時候釋放資源需要實現 IDisposable 介面
  2. 需要依賴注入的測試類實現介面 IClassFixture<Fixture>
  3. 在構造方法中注入實現的 Fixture 物件,並在構造方法中使用 Fixture 物件中暴露的公共成員

Singleton

針對 Singleton 的物件可以藉助 XUnit 中的 ICollectionFixture 來實現

  1. 定義自己的 Fixture,需要初始化的資源在構造方法裡初始化,如果需要在測試結束的時候釋放資源需要實現 IDisposable 介面
  2. 建立 CollectionDefinition,實現介面 ICollectionFixture<Fixture>,並新增一個 [CollectionDefinition("CollectionName")] Attribute,CollectionName 需要在整個測試中唯一,不能出現重複的 CollectionName
  3. 在需要注入的測試類中新增 [Collection("CollectionName")] Attribute,然後在構造方法中注入對應的 Fixture

Tips

  • 如果有多個類需要依賴注入,可以通過一個基類來做,這樣就只需要一個基類上新增 [Collection("CollectionName")] Attribute,其他類只需要整合這個基類就可以了

Samples

Scoped Sample

這裡直接以 XUnit 的示例為例:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
    DatabaseFixture fixture;

    public MyDatabaseTests(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }


    [Fact]
    public async Task GetTest()
    {
        // ... write tests, using fixture.Db to get access to the SQL Server ...
        // ... 在這裡使用注入 的 DatabaseFixture
    }
}

Singleton Sample

這裡以一個對 Controller 測試的測試為例

  1. 自定義 Fixture

        /// <summary>
        /// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
        /// </summary>
        public class TestStartupFixture : IDisposable
        {
            private readonly IWebHost _server;
            public IServiceProvider Services { get; }
    
            public HttpClient Client { get; }
    
            public string ServiceBaseUrl { get; }
    
            public TestStartupFixture()
            {
                var builder = WebHost.CreateDefaultBuilder()
                    .UseUrls($"http://localhost:{GetRandomPort()}")
                    .UseStartup<TestStartup>();
    
                _server = builder.Build();
                _server.Start();
    
                var url = _server.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First();
                Services = _server.Services;
                ServiceBaseUrl = $"{url}/api/";
    
                Client = new HttpClient()
                {
                    BaseAddress = new Uri(ServiceBaseUrl)
                };
    
                Initialize();
            }
    
            /// <summary>
            /// TestDataInitialize
            /// </summary>
            private void Initialize()
            {
                // ...
            }
    
            public void Dispose()
            {
                Client.Dispose();
                _server.Dispose();
            }
    
            private static readonly Random Random = new Random();
    
            private static int GetRandomPort()
            {
                var activePorts = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Select(_ => _.Port).ToList();
    
                var randomPort = Random.Next(10000, 65535);
    
                while (activePorts.Contains(randomPort))
                {
                    randomPort = Random.Next(10000, 65535);
                }
    
                return randomPort;
            }
        }
  2. 自定義Collection

        [CollectionDefinition("TestCollection")]
        public class TestCollection : ICollectionFixture<TestStartupFixture>
        {
        }
  3. 自定義一個 TestBase

        [Collection("TestCollection")]
        public class ControllerTestBase
        {
            protected readonly HttpClient Client;
            protected readonly IServiceProvider ServiceProvider;
    
            public ControllerTestBase(TestStartupFixture fixture)
            {
                Client = fixture.Client;
                ServiceProvider = fixture.Services;
            }
        }
  4. 需要依賴注入的Test類寫法

    public class AttendancesTest : ControllerTestBase
    {
        public AttendancesTest(TestStartupFixture fixture) : base(fixture)
        {
        }

        [Fact]
        public async Task GetAttendances()
        {
            var response = await Client.GetAsync("attendances");
            Assert.Equal(HttpStatusCode.OK, response.StatusCode);

            response = await Client.GetAsync("attendances?type=1");
            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        }
    }

Reference

Contact

如果您有什麼問題,歡迎隨時聯絡我

Contact me: weihanli@outlook.com

相關文章