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

这篇具有很好参考价值的文章主要介绍了实战指南:使用 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.DependencyInjectionxUnit单元测试项目启动时会检测是否有默认的Startup

如果你安装了Xunit.DependencyInjection但是还没有准备好在项目中使用也可以在csproj中禁用

<Project>
    <PropertyGroup>
        <EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>false</EnableXunitDependencyInjectionDefaultTestFrameworkAttribute>
    </PropertyGroup>
</Project>

再测试一下

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

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

配置

在我们的测试项目中新建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;
    }

所以我们只需要在单元测试项目的StartupConfigureServices 注入即可。
对我们的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

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

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

扩展

如何注入 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:

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

日志输出到 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 在单元测试中实现依赖注入【完整教程】文章来源地址https://www.toymoban.com/news/detail-852077.html

  • Xunit.DependencyInjection Github
  • 本文完整源代码

到了这里,关于实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完整教程】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • XUnit单元测试(实用项目)——看完不会用你打我

    xUnit.net 是针对 .NET 的免费、开源单元测试框架,可并行测试、数据驱动测试。测试项目需要同时引用 xUnit和被测试项目,从而对其进行测试。测试编写完成后,用 Test Runner 来测试项目,Test Runner 可以读取测试代码,并且知道所会使用的测试框架,然后执行,并显示结果。

    2024年02月04日
    浏览(34)
  • 【回眸】Tessy单元测试软件使用指南(一)安装篇

    在官网上下载安装包,安装完成后打开进入这个界面 注册申请license:在作为服务端的电脑上安装Tessy。安装完成后,启动Tessy会自动生成license服务器的注册码。(注册码用于申请试用或永久的license文件)这个对于我们来说没有啥太大的用处 点击 “server”——两个电脑的标志

    2024年02月05日
    浏览(73)
  • [AIGC] 使用Spring Boot进行单元测试:一份指南

    在现代软件开发过程中,确认你的应用正确运行是至关重要的一步。Spring Boot提供了一组实用工具和注解来辅助你在测试你的应用时,使得这个过程变得简单。下面就来分享一下如何在Spring Boot中进行单元测试。 单元测试是软件测试的基石。通过对代码的各个模块进行隔离测

    2024年04月11日
    浏览(36)
  • 测试人进阶技能:单元测试报告应用指南

    为什么需要单元测试 从产品角度而言,常规的功能测试、系统测试都是站在产品局部或全局功能进行测试,能够很好地与用户的需要相结合,但是缺乏了对产品研发细节(特别是代码细节的理解)。 从测试人员角度而言,功能测试和系统测试以及其他性能测试等等对测试人

    2024年02月13日
    浏览(27)
  • 自动化测试集成指南 -- 本地单元测试

    单元测试 (Unit Test) 是针对 程序的最小单元 来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。一个单元可能是 单个程序、类、对象、方法 等。 如何区分单元测试和集成测试,一般情况下,单元测试应该不依赖数据库,网络,I/O和其他模块,否则就是集成测

    2024年02月16日
    浏览(48)
  • 工程化测试:Apollo的单元测试与集成测试指南

    「作者主页」 :雪碧有白泡泡 「个人网站」 :雪碧的个人网站 「推荐专栏」 : ★ java一站式服务 ★ ★ React从入门到精通 ★ ★ 前端炫酷代码分享 ★ ★ 从0到英雄,vue成神之路★ ★ uniapp-从构建到提升 ★ ★ 从0到英雄,vue成神之路 ★ ★ 解决算法,一个专栏就够了 ★ ★

    2024年02月06日
    浏览(23)
  • XUnit数据共享与并行测试

    在单元或者集成测试的过程中,需要测试的用例非常多,如果测试是一条一条过,那么需要花费不少的时间。从 V2 开始,默认情况下 XUnit 自动配置并行(参考资料),大大提升了测试速度。本文将对 ASP.NET CORE WEBAPI 程序进行集成测试,并探讨 XUnit 的数据共享与测试并行的方

    2024年02月03日
    浏览(45)
  • Spring Boot单元测试入门指南

    JUnit是一个成熟和广泛应用的Java单元测试框架,它提供了丰富的功能和灵活的扩展机制,可以帮助开发人员编写高质量的单元测试。通过JUnit,开发人员可以更加自信地进行重构、维护和改进代码,同时提高代码质量和可维护性。 在使用Spring Boot进行单元测试时,以下是一些

    2024年02月15日
    浏览(31)
  • 单元测试实战(一)Controller 的测试

    为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试    单元测试实战(三)JPA 的测试     单元测试实战(四)MyBatis-Plus 的测试 单元测试实

    2024年02月04日
    浏览(61)
  • 单元测试实战(二)Service 的测试

    为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试 单元测试实战(三)JPA 的测试     单元测试实战(四)MyBatis-Plus 的测试 单元测试实战(

    2024年02月04日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包