实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完整教程】
引言
上一篇我们创建了一个Sample.Api项目和Sample.Repository,并且带大家熟悉了一下Moq的概念,这一章我们来实战一下在xUnit项目使用依赖注入。
Xunit.DependencyInjection
Xunit.DependencyInjection 是一个用于 xUnit 测试框架的扩展库,它提供了依赖注入的功能,使得在编写单元测试时可以更方便地进行依赖注入。通过使用 Xunit.DependencyInjection,可以在 xUnit 测试中使用依赖注入容器(比如 Microsoft.Extensions.DependencyInjection)来管理测试中所需的各种依赖关系,包括服务、日志、配置等等。
使用
我们用Xunit.DependencyInjection对上一章的Sample.Repository进行单元测试。
Nuget包安装项目依赖
PM> NuGet\Install-Package Xunit.DependencyInjection -Version 9.1.0
创建测试类
public class StaffRepositoryTest
{
[Fact]
public void DependencyInject_WhenCalled_ReturnTrue()
{
Assert.True(true);
}
}
运行测试 先看一下

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

可以看到我们添加的配置生效了
配置
在我们的测试项目中新建Startup.cs
public class Startup
{
}
在.Net 6 之前我们不就是用这个来配置项目的依赖和管道吗,其实这个位置也一样用它来对我们项目的依赖和服务做一些基础配置,使用配置单元测试的Startup其实和配置我们的Asp.Net Core的启动配置是一样的
CreateHostBuilder
CreateHostBuilder 方法用于创建应用程序的主机构建器(HostBuilder)。在这个方法中,您可以配置主机的各种参数、服务、日志、环境等。这个方法通常用于配置主机构建器的各种属性,以便在应用程序启动时使用。
public IHostBuilder CreateHostBuilder([AssemblyName assemblyName]) { }
ConfigureHost
ConfigureHost 方法用于配置主机构建器。在这个方法中,您可以对主机进行一些自定义的配置,比如设置环境、使用特定的配置源等
public void ConfigureHost(IHostBuilder hostBuilder) { }
ConfigureServices
ConfigureServices 方法用于配置依赖注入容器(ServiceCollection)。在这个方法中,您可以注册应用程序所需的各种服务、中间件、日志、数据库上下文等等。这个方法通常用于配置应用程序的依赖注入服务。
Configure
ConfigureServices 中配置的服务可以在 Configure 方法中指定。如果已经配置的服务在 Configure 方法的参数中可用,它们将会被注入
public void Configure()
{
}
Sample.Repository
接下来对我们的仓储层进行单元测试
已知我们的仓储层已经有注入的扩展方法
public static IServiceCollection AddEFCoreInMemoryAndRepository(this IServiceCollection services)
{
services.AddScoped<IStaffRepository, StaffRepository>();
services.AddDbContext<SampleDbContext>(options => options.UseInMemoryDatabase("sample").EnableSensitiveDataLogging(), ServiceLifetime.Scoped);
return services;
}
所以我们只需要在单元测试项目的Startup的ConfigureServices 注入即可。
对我们的Sample.Repository添加项目引用,然后进行依赖注册
public void ConfigureServices(IServiceCollection services, HostBuilderContext context)
{
services.AddEFCoreInMemoryAndRepository();
}
好了接下来编写单元测试Case
依赖项获取:
public class StaffRepositoryTest
{
private readonly IStaffRepository _staffRepository;
public StaffRepositoryTest(IStaffRepository staffRepository)
{
_staffRepository = staffRepository;
}
}
在测试类中使用依赖注入和我们正常获取依赖是一样的都是通过构造函数的形式
public class StaffRepositoryTest
{
private readonly IStaffRepository _staffRepository;
public StaffRepositoryTest(IStaffRepository staffRepository)
{
_staffRepository = staffRepository;
}
//[Fact]
//public void DependencyInject_WhenCalled_ReturnTrue()
//{
// Assert.True(true);
//}
[Fact]
public async Task AddStaffAsync_WhenCalled_ShouldAddStaffToDatabase()
{
// Arrange
var staff = new Staff { Name = "zhangsan", Email = "zhangsan@163.com" };
// Act
await _staffRepository.AddStaffAsync(staff, CancellationToken.None);
// Assert
var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None);
Assert.NotNull(retrievedStaff); // 确保 Staff 已成功添加到数据库
Assert.Equal("zhangsan", retrievedStaff.Name); // 检查名称是否正确
}
[Fact]
public async Task DeleteStaffAsync_WhenCalled_ShouldDeleteStaffFromDatabase()
{
var staff = new Staff { Name = "John", Email = "john@example.com" };
await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
// Act
await _staffRepository.DeleteStaffAsync(staff.Id, CancellationToken.None); // 删除该 Staff
// Assert
var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 尝试获取已删除的 Staff
Assert.Null(retrievedStaff); // 确保已经删除
}
[Fact]
public async Task UpdateStaffAsync_WhenCalled_ShouldUpdateStaffInDatabase()
{
// Arrange
var staff = new Staff { Name = "John", Email = "john@example.com" };
await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
// Act
staff.Name = "Updated Name";
await _staffRepository.UpdateStaffAsync(staff, CancellationToken.None); // 更新 Staff
// Assert
var updatedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 获取已更新的 Staff
Assert.Equal("Updated Name", updatedStaff?.Name); // 确保 Staff 已更新
}
[Fact]
public async Task GetStaffByIdAsync_WhenCalledWithValidId_ShouldReturnStaffFromDatabase()
{
// Arrange
var staff = new Staff { Name = "John", Email = "john@example.com" };
await _staffRepository.AddStaffAsync(staff, CancellationToken.None); // 先添加一个 Staff
// Act
var retrievedStaff = await _staffRepository.GetStaffByIdAsync(staff.Id, CancellationToken.None); // 获取 Staff
// Assert
Assert.NotNull(retrievedStaff); // 确保成功获取 Staff
}
[Fact]
public async Task GetAllStaffAsync_WhenCalled_ShouldReturnAllStaffFromDatabase()
{
// Arrange
var staff1 = new Staff { Name = "John", Email = "john@example.com" };
var staff2 = new Staff { Name = "Alice", Email = "alice@example.com" };
await _staffRepository.AddStaffAsync(staff1, CancellationToken.None); // 先添加 Staff1
await _staffRepository.AddStaffAsync(staff2, CancellationToken.None); // 再添加 Staff2
// Act
var allStaff = await _staffRepository.GetAllStaffAsync(CancellationToken.None); // 获取所有 Staff
// Assert
List<Staff> addStaffs = [staff1, staff2];
Assert.True(addStaffs.All(_ => allStaff.Any(x => x.Id == _.Id))); // 确保成功获取所有 Staff
}
}
Run Tests

可以看到单元测试已经都成功了,是不是很简单呢。
扩展
如何注入 ITestOutputHelper?
之前的示例不使用xUnit.DependencyInjection我们用ITestOutputHelper通过构造函数构造,现在是用ITestOutputHelperAccessor
public class DependencyInjectionTest
{
private readonly ITestOutputHelperAccessor _testOutputHelperAccessor;
public DependencyInjectionTest(ITestOutputHelperAccessor testOutputHelperAccessor)
{
_testOutputHelperAccessor = testOutputHelperAccessor;
}
[Fact]
public void TestOutPut_Console()
{
_testOutputHelperAccessor.Output?.WriteLine("测试ITestOutputHelperAccessor");
Assert.True(true);
}
}
OutPut:

日志输出到 ITestOutputHelper
Nuget安装
PM> NuGet\Install-Package Xunit.DependencyInjection.Logging -Version 9.0.0
ConfigureServices配置依赖
public void ConfigureServices(IServiceCollection services)
=> services.AddLogging(lb => lb.AddXunitOutput());
使用:
public class DependencyInjectionTest
{
private readonly ILogger<DependencyInjectionTest> _logger;
public DependencyInjectionTest(ILogger<DependencyInjectionTest> logger)
{
_logger = logger;
}
[Fact]
public void Test()
{
_logger.LogDebug("LogDebug");
_logger.LogInformation("LogInformation");
_logger.LogError("LogError");
}
}
OutPut:
标准输出:
[2024-04-12 16:00:24Z] info: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
LogInformation
[2024-04-12 16:00:24Z] fail: dotNetParadise.DependencyInjection.DependencyInjectionTest[0]
LogError
startup 类中注入 IConfiguration 或 IHostEnvironment
通过ConfigureServices设置 EnvironmentName和使用IConfiguration
public void ConfigureServices(HostBuilderContext context)
{
context.HostingEnvironment.EnvironmentName = "test";
//使用配置
context.Configuration.GetChildren();
}
也可以使用Startup下的ConfigureHost设置
public class Startup
{
public void ConfigureHost(IHostBuilder hostBuilder) =>
hostBuilder
.ConfigureServices((context, services) => { context.XXXX });
}
在 ConfigureHost 下可以对.Net IHostBuilder进行配置,可以对IConfiguration,IServiceCollection,Log等跟Asp.Net Core使用一致。
集成测试
xUnit.DependencyInjection 也可以对Asp.Net Core项目进行集成测试
安装 Microsoft.AspNetCore.TestHost
PM> NuGet\Install-Package Microsoft.AspNetCore.TestHost -Version 9.0.0-preview.3.24172.13
public void ConfigureHost(IHostBuilder hostBuilder) =>
hostBuilder.ConfigureWebHost[Defaults](webHostBuilder => webHostBuilder
.UseTestServer(options => options.PreserveExecutionContext = true)
.UseStartup<AspNetCoreStartup>());
可以参考 xUnit 的官网实现,其实有更优雅的实现集成测试的方案,xUnit.DependencyInject 的集成测试方案仅做参考集合,在后面章节笔者会对集成测试做详细的介绍。
最后
希望本文对您在使用 Xunit.DependencyInjection 进行依赖注入和编写单元测试时有所帮助。通过本文的介绍,您可以更加灵活地管理测试项目中的依赖关系,提高测试代码的可维护性和可测试性
欢迎关注笔者公众号一起学习交流,获取更多有用的知识~

实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完整教程】的更多相关文章
- ASP.NET 5 单元测试中使用依赖注入
相关博文:<ASP.NET 5 使用 TestServer 进行单元测试> 在上一篇博文中,主要说的是,使用 TestServer 对 ASP.NET 5 WebApi 进行单元测试,依赖 ...
- 在 XUnit 中使用依赖注入
在 XUnit 中使用依赖注入 Intro 之前写过一篇 xunit 的依赖注入相关的文章,但是实际使用起来不是那么方便 今天介绍一个基于xunit和微软依赖注入框架的"真正"的依 ...
- 更优雅的在 Xunit 中使用依赖注入
Xunit.DependencyInjection 7.0 发布了 Intro 上次我们已经介绍过一次大师的 Xunit.DependencyInjection https://www.cnblogs ...
- 在 xunit 测试项目中使用依赖注入
在 xunit 测试项目中使用依赖注入 Intro 之前写过几篇 xunit 依赖注入的文章,今天这篇文章将结合我在 .NET Conf 上的分享,更加系统的分享一下在测试中的应用案例. 之所以想分享 ...
- ASP.NET Core 在 JSON 文件中配置依赖注入
前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...
- 在.NET Core控制台程序中使用依赖注入
之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- Spring学习(三)——Spring中的依赖注入的方式
[前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...
- [.net 面向对象程序设计深入](26)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)
[.net 面向对象程序设计深入](26)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1) 1,关于IOC模式 先看一些名词含义: IOC: Inversion of con ...
随机推荐
- 多线程系列(十一) -浅析并发读写锁StampedLock
一.摘要 在上一篇文章中,我们讲到了使用ReadWriteLock可以解决多线程同时读,但只有一个线程能写的问题. 如果继续深入的分析ReadWriteLock,从锁的角度分析,会发现它有一个潜在的问 ...
- Codeforces Round 787 (Div. 3)D. Vertical Paths
题目链接 题意:给定一棵树,将这棵树划分成几天互不相交的链,要求最小化链的数量 思路:每个叶子节点一定在一条链中,所以链的数量就是叶子节点的数量,从叶子节点往上跳直到根节点,边跳边标记,路径上所有点都 ...
- .Net下的简易Http请求调用(Post与Get)
http请求调用是开发中经常会用到的功能.在内,调用自有项目的Web Api等形式接口时会用到:在外,调用一些第三方功能接口时,也会用到,因为,这些第三方功能往往是通过http地址的形式提供的,比如: ...
- java服务OOM和CPU飙升排查
一.JVM参数 -D 可以是系统默认有的参数,也可以是自己定义的参数 -Dfile.encoding=UTF-8 -Dmaven.test.skip=true -Dspring.profiles.ac ...
- Caxa 二次开发 ObjectCRX-1 踩坑:环境配置以及 Helloworld
绝了,坑是真 nm 的多,官方给的文档里到处都是坑. 用的环境 ObjectCRX,以下简称 objcrx. #1 安装环境 & 参考文档的大坑 #1.1 Caxa 提供的文档和环境安装包 首 ...
- webserver总结
可设置参数:连接池最大连接数,最大线程数,任务队列最大值,timeslot epoll epoll监听listen_fd(接受新客户端和断开连接), pipefd(将信号输入到管道用epoll统一管理 ...
- FFmpeg命令行之ffplay
一.简述 ffplay是以FFmpeg框架为基础,外加渲染音视频的库libSDL构建的媒体文件播放器. 二.命令格式 在安装了在命令行中输入如下格式的命令: ffplay [选项] ['输入文件'] ...
- 3DCAT云流送技术如何搅动各大行业
在不久前结束的数字会展上,3DCAT展位的实时云流送技术技惊四座,带来不一样的视觉体验,到访的客户都无法分辨这些数字内容是在本地还是云端运行的,每一个粒子都真实可见,有现场的参观者瞪大了双眼,直呼不可 ...
- 记录--关于无感刷新Token,我是这样子做的
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 什么是JWT JWT是全称是JSON WEB TOKEN,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可 ...
- Spring Boot框架中针对数据文件模板的下载总结
1.前言 在我们的日常开发中,经常会碰到注入导入Excel数据到系统中的需求,而在导入Excel数据时,一般的业务系统都会提供数据的Excel模板,只有提交的Excel数据满足业务系统要求的模板时,数 ...