XUnit 依賴注入
Intro
現(xiàn)在的開發(fā)中越來(lái)越看重依賴注入的思想腾夯,微軟的 Asp.Net Core 框架更是天然集成了依賴注入,那么在單元測(cè)試中如何使用依賴注入呢既棺?
本文主要介紹如何通過 XUnit 來(lái)實(shí)現(xiàn)依賴注入璃吧, XUnit 主要借助 SharedContext 來(lái)共享一部分資源包括這些資源的創(chuàng)建以及釋放饮怯。
Scoped
針對(duì) Scoped 的對(duì)象可以借助 XUnit 中的 IClassFixture 來(lái)實(shí)現(xiàn)
- 定義自己的 Fixture,需要初始化的資源在構(gòu)造方法里初始化榛泛,如果需要在測(cè)試結(jié)束的時(shí)候釋放資源需要實(shí)現(xiàn)
IDisposable
接口 - 需要依賴注入的測(cè)試類實(shí)現(xiàn)接口
IClassFixture<Fixture>
- 在構(gòu)造方法中注入實(shí)現(xiàn)的 Fixture 對(duì)象蝌蹂,并在構(gòu)造方法中使用 Fixture 對(duì)象中暴露的公共成員
Singleton
針對(duì) Singleton 的對(duì)象可以借助 XUnit 中的 ICollectionFixture 來(lái)實(shí)現(xiàn)
- 定義自己的
Fixture
,需要初始化的資源在構(gòu)造方法里初始化曹锨,如果需要在測(cè)試結(jié)束的時(shí)候釋放資源需要實(shí)現(xiàn)IDisposable
接口 - 創(chuàng)建 CollectionDefinition孤个,實(shí)現(xiàn)接口
ICollectionFixture<Fixture>
,并添加一個(gè)[CollectionDefinition("CollectionName")]
Attribute艘希,CollectionName
需要在整個(gè)測(cè)試中唯一硼身,不能出現(xiàn)重復(fù)的CollectionName
- 在需要注入的測(cè)試類中添加
[Collection("CollectionName")]
Attribute,然后在構(gòu)造方法中注入對(duì)應(yīng)的Fixture
Tips
- 如果有多個(gè)類需要依賴注入覆享,可以通過一個(gè)基類來(lái)做佳遂,這樣就只需要一個(gè)基類上添加
[Collection("CollectionName")]
Attribute,其他類只需要集成這個(gè)基類就可以了
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
這里以一個(gè)對(duì) Controller 測(cè)試的測(cè)試為例
-
自定義 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> { }
-
自定義一個(gè) 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
如果您有什么問題撒顿,歡迎隨時(shí)聯(lián)系我
Contact me: weihanli@outlook.com