XUnit 依賴注入
Intro
現在的開發中越來越看重依賴注入的思想,微軟的 Asp.Net Core 框架更是天然整合了依賴注入,那麼在單元測試中如何使用依賴注入呢?
本文主要介紹如何通過 XUnit 來實現依賴注入, XUnit 主要藉助 SharedContext 來共享一部分資源包括這些資源的建立以及釋放。
Scoped
針對 Scoped 的物件可以藉助 XUnit 中的 IClassFixture 來實現
- 定義自己的 Fixture,需要初始化的資源在構造方法裡初始化,如果需要在測試結束的時候釋放資源需要實現
IDisposable
介面 - 需要依賴注入的測試類實現介面
IClassFixture<Fixture>
- 在構造方法中注入實現的 Fixture 物件,並在構造方法中使用 Fixture 物件中暴露的公共成員
Singleton
針對 Singleton 的物件可以藉助 XUnit 中的 ICollectionFixture 來實現
- 定義自己的
Fixture
,需要初始化的資源在構造方法裡初始化,如果需要在測試結束的時候釋放資源需要實現IDisposable
介面 - 建立 CollectionDefinition,實現介面
ICollectionFixture<Fixture>
,並新增一個[CollectionDefinition("CollectionName")]
Attribute,CollectionName
需要在整個測試中唯一,不能出現重複的CollectionName
- 在需要注入的測試類中新增
[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 測試的測試為例
-
自定義 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; } }
-
自定義Collection
[CollectionDefinition("TestCollection")] public class TestCollection : ICollectionFixture<TestStartupFixture> { }
-
自定義一個 TestBase
[Collection("TestCollection")] public class ControllerTestBase { protected readonly HttpClient Client; protected readonly IServiceProvider ServiceProvider; public ControllerTestBase(TestStartupFixture fixture) { Client = fixture.Client; ServiceProvider = fixture.Services; } }
-
需要依賴注入的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