現在的開發中越來越看重依賴註入的思想,微軟的 Asp.Net Core 框架更是天然集成了依賴註入,那麼在單元測試中如何使用依賴註入呢? 本文主要介紹如何通過 XUnit 來實現依賴註入, XUnit 主要藉助 SharedContext 來共用一部分資源包括這些資源的創建以及釋放。 ...
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: [email protected]