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; } [ ] 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
[public class TestCollection : ICollectionFixture<TestStartupFixture> { }
] -
自定义一个 TestBase
[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) { } [ ] 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#
- https://xunit.github.io/docs/shared-context.html
Contact#
如果您有什么问题,欢迎随时联系我
Contact me: weihanli@outlook.com