翼度科技»论坛 编程开发 .net 查看内容

实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
引言

上一篇我们创建了一个Sample.Api项目和Sample.Repository,并且带大家熟悉了一下Moq的概念,这一章我们来实战一下在xUnit项目使用依赖注入。
Xunit.DependencyInjection

Xunit.DependencyInjection 是一个用于 xUnit 测试框架的扩展库,它提供了依赖注入的功能,使得在编写单元测试时可以更方便地进行依赖注入。通过使用 Xunit.DependencyInjection,可以在 xUnit 测试中使用依赖注入容器(比如 Microsoft.Extensions.DependencyInjection)来管理测试中所需的各种依赖关系,包括服务、日志、配置等等。
使用

我们用Xunit.DependencyInjection对上一章的Sample.Repository进行单元测试。
Nuget包安装项目依赖
  1. PM> NuGet\Install-Package Xunit.DependencyInjection -Version 9.1.0
复制代码
创建测试类
  1. public class StaffRepositoryTest
  2. {
  3.     [Fact]
  4.     public void DependencyInject_WhenCalled_ReturnTrue()
  5.     {
  6.         Assert.True(true);
  7.     }
  8. }
复制代码
运行测试 先看一下

从这可以得出一个结论 如果安装了Xunit.DependencyInjection的xUnit单元测试项目启动时会检测是否有默认的Startup类
如果你安装了Xunit.DependencyInjection但是还没有准备好在项目中使用也可以在csproj中禁用
  1. <Project>
  2.     <PropertyGroup>
  3.         <EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>false</EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>
  4.     </PropertyGroup>
  5. </Project>
复制代码
再测试一下

可以看到我们添加的配置生效了
配置

在我们的测试项目中新建Startup.cs
  1. public class Startup
  2. {
  3. }
复制代码
在.Net 6 之前我们不就是用这个来配置项目的依赖和管道吗,其实这个位置也一样用它来对我们项目的依赖和服务做一些基础配置,使用配置单元测试的Startup其实和配置我们的Asp.Net Core的启动配置是一样的
CreateHostBuilder

CreateHostBuilder 方法用于创建应用程序的主机构建器(HostBuilder)。在这个方法中,您可以配置主机的各种参数、服务、日志、环境等。这个方法通常用于配置主机构建器的各种属性,以便在应用程序启动时使用。
  1. public IHostBuilder CreateHostBuilder([AssemblyName assemblyName]) { }
复制代码
ConfigureHost

ConfigureHost 方法用于配置主机构建器。在这个方法中,您可以对主机进行一些自定义的配置,比如设置环境、使用特定的配置源等
  1.   public void ConfigureHost(IHostBuilder hostBuilder) { }
复制代码
ConfigureServices

ConfigureServices 方法用于配置依赖注入容器(ServiceCollection)。在这个方法中,您可以注册应用程序所需的各种服务、中间件、日志、数据库上下文等等。这个方法通常用于配置应用程序的依赖注入服务。
Configure

ConfigureServices 中配置的服务可以在 Configure 方法中指定。如果已经配置的服务在 Configure 方法的参数中可用,它们将会被注入
  1.     public void Configure()
  2.     {
  3.     }
复制代码
Sample.Repository

接下来对我们的仓储层进行单元测试
已知我们的仓储层已经有注入的扩展方法
  1.     public static IServiceCollection AddEFCoreInMemoryAndRepository(this IServiceCollection services)
  2.     {
  3.         services.AddScoped<IStaffRepository, StaffRepository>();
  4.         services.AddDbContext<SampleDbContext>(options => options.UseInMemoryDatabase("sample").EnableSensitiveDataLogging(), ServiceLifetime.Scoped);
  5.         return services;
  6.     }
复制代码
所以我们只需要在单元测试项目的Startup的ConfigureServices 注入即可。
对我们的Sample.Repository添加项目引用,然后进行依赖注册
  1.     public void ConfigureServices(IServiceCollection services, HostBuilderContext context)
  2.     {
  3.         services.AddEFCoreInMemoryAndRepository();
  4.     }
复制代码
好了接下来编写单元测试Case
依赖项获取:
  1. public class StaffRepositoryTest
  2. {
  3.     private readonly IStaffRepository _staffRepository;
  4.     public StaffRepositoryTest(IStaffRepository staffRepository)
  5.     {
  6.         _staffRepository = staffRepository;
  7.     }
  8. }
复制代码
在测试类中使用依赖注入和我们正常获取依赖是一样的都是通过构造函数的形式
  1. public class StaffRepositoryTest
  2. {
  3.     private readonly IStaffRepository _staffRepository;
  4.     public StaffRepositoryTest(IStaffRepository staffRepository)
  5.     {
  6.         _staffRepository = staffRepository;
  7.     }
  8.     //[Fact]
  9.     //public void DependencyInject_WhenCalled_ReturnTrue()
  10.     //{
  11.     //    Assert.True(true);
  12.     //}
  13.     [Fact]
  14.     public async Task AddStaffAsync_WhenCalled_ShouldAddStaffToDatabase()
  15.     {
  16.         // Arrange
  17.         var staff = new Staff { Name = "zhangsan", Email = "zhangsan@163.com" };
  18.         // Act
  19.         await _staffRepository.AddStaffAsync(staff, CancellationToken.None);
  20.         // Assert
  21.         var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None);
  22.         Assert.NotNull(retrievedStaff); // 确保 Staff 已成功添加到数据库
  23.         Assert.Equal("zhangsan", retrievedStaff.Name); // 检查名称是否正确
  24.     }
  25.     [Fact]
  26.     public async Task DeleteStaffAsync_WhenCalled_ShouldDeleteStaffFromDatabase()
  27.     {
  28.         var staff = new Staff { Name = "John", Email = "john@example.com" };
  29.         await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
  30.         // Act
  31.         await _staffRepository.DeleteStaffAsync(staff.Id, CancellationToken.None); // 删除该 Staff
  32.         // Assert
  33.         var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 尝试获取已删除的 Staff
  34.         Assert.Null(retrievedStaff); // 确保已经删除
  35.     }
  36.     [Fact]
  37.     public async Task UpdateStaffAsync_WhenCalled_ShouldUpdateStaffInDatabase()
  38.     {
  39.         // Arrange
  40.         var staff = new Staff { Name = "John", Email = "john@example.com" };
  41.         await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
  42.         // Act
  43.         staff.Name = "Updated Name";
  44.         await _staffRepository.UpdateStaffAsync(staff, CancellationToken.None); // 更新 Staff
  45.         // Assert
  46.         var updatedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 获取已更新的 Staff
  47.         Assert.Equal("Updated Name", updatedStaff?.Name); // 确保 Staff 已更新
  48.     }
  49.     [Fact]
  50.     public async Task GetStaffByIdAsync_WhenCalledWithValidId_ShouldReturnStaffFromDatabase()
  51.     {
  52.         // Arrange
  53.         var staff = new Staff { Name = "John", Email = "john@example.com" };
  54.         await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
  55.                                                                              // Act
  56.         var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 获取 Staff
  57.                                                                                                          // Assert
  58.         Assert.NotNull(retrievedStaff); // 确保成功获取 Staff
  59.     }
  60.     [Fact]
  61.     public async Task GetAllStaffAsync_WhenCalled_ShouldReturnAllStaffFromDatabase()
  62.     {
  63.         // Arrange
  64.         var staff1 = new Staff { Name = "John", Email = "john@example.com" };
  65.         var staff2 = new Staff { Name = "Alice", Email = "alice@example.com" };
  66.         await _staffRepository.AddStaffAsync(staff1, CancellationToken.None); // 先添加 Staff1
  67.         await _staffRepository.AddStaffAsync(staff2, CancellationToken.None); // 再添加 Staff2
  68.         // Act
  69.         var allStaff = await _staffRepository.GetAllStaffAsync(CancellationToken.None); // 获取所有 Staff
  70.         // Assert
  71.         List<Staff> addStaffs = [staff1, staff2];
  72.         Assert.True(addStaffs.All(_ => allStaff.Any(x => x.Id == _.Id))); // 确保成功获取所有 Staff
  73.     }
  74. }
复制代码
Run Tests

可以看到单元测试已经都成功了,是不是很简单呢。
扩展

如何注入 ITestOutputHelper?

之前的示例不使用xUnit.DependencyInjection我们用ITestOutputHelper通过构造函数构造,现在是用ITestOutputHelperAccessor
  1. public class DependencyInjectionTest
  2. {
  3.     private readonly ITestOutputHelperAccessor _testOutputHelperAccessor;
  4.     public DependencyInjectionTest(ITestOutputHelperAccessor testOutputHelperAccessor)
  5.     {
  6.         _testOutputHelperAccessor = testOutputHelperAccessor;
  7.     }
  8.     [Fact]
  9.     public void TestOutPut_Console()
  10.     {
  11.         _testOutputHelperAccessor.Output?.WriteLine("测试ITestOutputHelperAccessor");
  12.         Assert.True(true);
  13.     }
  14. }
复制代码
OutPut:

日志输出到 ITestOutputHelper

Nuget安装
  1. PM> NuGet\Install-Package Xunit.DependencyInjection.Logging -Version 9.0.0
复制代码
ConfigureServices配置依赖
  1. public void ConfigureServices(IServiceCollection services)
  2.         => services.AddLogging(lb => lb.AddXunitOutput());
复制代码
使用:
  1. public class DependencyInjectionTest
  2. {
  3.     private readonly ILogger<DependencyInjectionTest> _logger;
  4.     public DependencyInjectionTest(ILogger<DependencyInjectionTest> logger)
  5.     {
  6.         _logger = logger;
  7.     }
  8.     [Fact]
  9.     public void Test()
  10.     {
  11.         _logger.LogDebug("LogDebug");
  12.         _logger.LogInformation("LogInformation");
  13.         _logger.LogError("LogError");
  14.     }
  15. }
复制代码
OutPut:
  1. 标准输出: 
  2. [2024-04-12 16:00:24Z] info: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
  3.       LogInformation
  4. [2024-04-12 16:00:24Z] fail: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
  5.       LogError
复制代码
startup 类中注入 IConfiguration 或 IHostEnvironment

通过ConfigureServices设置 EnvironmentName和使用IConfiguration
  1.    public void ConfigureServices(HostBuilderContext context)
  2.     {
  3.         context.HostingEnvironment.EnvironmentName = "test";
  4.            //使用配置
  5.         context.Configuration.GetChildren();
  6.     }
复制代码
也可以使用Startup下的ConfigureHost设置
  1. public class Startup
  2. {
  3.     public void ConfigureHost(IHostBuilder hostBuilder) =>
  4.         hostBuilder
  5.             .ConfigureServices((context, services) => { context.XXXX });
  6. }
复制代码
在 ConfigureHost 下可以对.Net IHostBuilder进行配置,可以对IConfiguration,IServiceCollection,Log等跟Asp.Net Core使用一致。
集成测试

xUnit.DependencyInjection 也可以对Asp.Net Core项目进行集成测试
安装 Microsoft.AspNetCore.TestHost
  1. PM> NuGet\Install-Package Microsoft.AspNetCore.TestHost -Version 9.0.0-preview.3.24172.13
复制代码
  1.     public void ConfigureHost(IHostBuilder hostBuilder) =>
  2.         hostBuilder.ConfigureWebHost[Defaults](webHostBuilder => webHostBuilder
  3.             .UseTestServer(options => options.PreserveExecutionContext = true)
  4.             .UseStartup<AspNetCoreStartup>());
复制代码
可以参考 xUnit 的官网实现,其实有更优雅的实现集成测试的方案,xUnit.DependencyInject 的集成测试方案仅做参考集合,在后面章节笔者会对集成测试做详细的介绍。
最后

希望本文对您在使用 Xunit.DependencyInjection 进行依赖注入和编写单元测试时有所帮助。通过本文的介绍,您可以更加灵活地管理测试项目中的依赖关系,提高测试代码的可维护性和可测试性

<em>
来源:https://www.cnblogs.com/ruipeng/p/18134907
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具