Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入

Posted

技术标签:

【中文标题】Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入【英文标题】:Net Core: Execute All Dependency Injection in Xunit Test for AppService, Repository, etc 【发布时间】:2019-12-11 08:55:22 【问题描述】:

我正在尝试在 AppService 的 Xunit 测试中实现依赖注入。理想的目标是运行原始应用程序启动/配置,并使用启动中的任何依赖注入,而不是在我的测试中重新初始化所有 DI,这就是整个目标。

更新:Mohsen 的答案很接近。需要更新几个语法/要求错误才能工作。

由于某种原因,原始应用程序可以运行并且可以调用部门应用程序服务。但是,它不能调用 Xunit。最后使用原始应用程序中的启动和配置使测试服务器正常工作。 现在收到以下错误:

Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService

namespace Testing.IntegrationTests

    public class DepartmentAppServiceTest
    
        public DBContext context;
        public IDepartmentAppService departmentAppService;

        public DepartmentAppServiceTest(IDepartmentAppService departmentAppService)
        
            this.departmentAppService = departmentAppService;
        

        [Fact]
        public async Task Get_DepartmentById_Are_Equal()
        
            var options = new DbContextOptionsBuilder<SharedServicesContext>()
                .UseInMemoryDatabase(databaseName: "TestDatabase")
                .Options;
            context = new DBContext(options);

            TestServer _server = new TestServer(new WebHostBuilder()
                .UseContentRoot("C:\\OriginalApplication")
                .UseEnvironment("Development")
                .UseConfiguration(new ConfigurationBuilder()
                    .SetBasePath("C:\\OriginalApplication")
                    .AddJsonFile("appsettings.json")
                    .Build()).UseStartup<Startup>());

            context.Department.Add(new Department  DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" );
            context.SaveChanges();

            var departmentDto = await departmentAppService.GetDepartmentById(2);

            Assert.Equal("123", departmentDto.DepartmentCode);
        
    

我收到此错误:

Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService

需要像实际应用一样在测试中使用依赖注入。 原始应用程序执行此操作。下面的答案目前还不够,一个使用了不是当前目标的模拟,另一个答案使用了绕过问题目的的控制器。

注意:IDepartmentAppService 对 IDepartmentRepository 有依赖关系,IDepartmentRepository 也注入到 Startup 类中,以及 Automapper 依赖关系。这就是调用整个启动类的原因。

好资源:

how to unit test asp.net core application with constructor dependency injection

Dependency injection in Xunit project

【问题讨论】:

您想在控制器级别还是应用服务层进行测试? 你的需求有点混乱,XUnit是一个单元测试框架,它不是一个集成测试框架,你几乎是在尝试用单元测试框架创建一个集成测试,这必然会产生混淆。 DI 将在应用程序中通过解决所有依赖项的入口点发生,而不是通过 xunit 测试用例。您应该在 XUnit 中使用 Mocking 框架。 @user11860043 我在我的旧答案上看到了你的 cmets。正在度假。你的问题现在已经回答了吗?请标记答案,然后请。如果没有,我可以在本周的某个地方查看它。谢谢。 试试 xunit 框架中内置的 xunit di 支持:nuget.org/packages/Xunit.Di,这样您就可以像对任何其他应用程序一样注入服务依赖项。 【参考方案1】:

您正在混合单元测试和集成测试。 TestServer 用于集成测试,如果您想重用 Startup 类以避免再次注册依赖项,则应使用 HttpClient 并对使用 IDepartmentAppService 的控制器和操作进行 HTTP 调用。

如果要进行单元测试,则需要设置 DI 并注册所有需要的依赖项来测试 IDepartmentAppService

通过测试夹具使用 DI:

public class DependencySetupFixture

    public DependencySetupFixture()
    
         var serviceCollection = new ServiceCollection();
         serviceCollection.AddDbContext<SharedServicesContext>(options => options.UseInMemoryDatabase(databaseName: "TestDatabase"));
         serviceCollection.AddTransient<IDepartmentRepository, DepartmentRepository>();
         serviceCollection.AddTransient<IDepartmentAppService, DepartmentAppService>();

         ServiceProvider = serviceCollection.BuildServiceProvider();
    

    public ServiceProvider ServiceProvider  get; private set; 


public class DepartmentAppServiceTest : IClassFixture<DependencySetupFixture>

    private ServiceProvider _serviceProvide;

    public DepartmentAppServiceTest(DependencySetupFixture fixture)
    
        _serviceProvide = fixture.ServiceProvider;
    

    [Fact]
    public async Task Get_DepartmentById_Are_Equal()
    
        using(var scope = _serviceProvider.CreateScope())
           
            // Arrange
            var context = scope.ServiceProvider.GetServices<SharedServicesContext>();
            context.Department.Add(new Department  DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" );
            context.SaveChanges();

            var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();

            // Act
            var departmentDto = await departmentAppService.GetDepartmentById(2);

            // Arrange
            Assert.Equal("123", departmentDto.DepartmentCode);           
        
    

在单元测试中使用依赖注入不是一个好主意,你应该避免这种情况。顺便说一句,如果您不想重复注册依赖项,您可以将您的 DI 配置包装在另一个类中,并在任何您想要的地方使用该类。

通过 Startup.cs 使用 DI:

public class IocConfig

    public static IServiceCollection Configure(IServiceCollection services, IConfiguration configuration)
    
         serviceCollection
            .AddDbContext<SomeContext>(options => options.UseSqlServer(configuration["ConnectionString"]));
         serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
         serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
         .
         .
         .

         return services;
    

Startup 类和ConfigureServices 方法中只使用IocConfig 类:

public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    public void ConfigureServices(IServiceCollection services)
    
         IocConfig.Configure(services, configuration);

         services.AddMvc();
         .
         .
         .

如果您不想使用 IocConfig 类,请将 ConfigureServices 更改为 Startup 类:

public IServiceCollection ConfigureServices(IServiceCollection services)

     .
     .
     .
     return services;

并在测试项目中重用 IocConfigStartup 类:

public class DependencySetupFixture

    public DependencySetupFixture()
    
          var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", false, true));
         configuration = builder.Build();

         var services = new ServiceCollection();

         // services = IocConfig.Configure(services, configuration)
         // or
         // services = new Startup(configuration).ConfigureServices(services);

         ServiceProvider = services.BuildServiceProvider();
    

    public ServiceProvider ServiceProvider  get; private set; 

在测试方法中:

[Fact]
public async Task Get_DepartmentById_Are_Equal()

    using (var scope = _serviceProvider.CreateScope())
    
        // Arrange
        var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();

        // Act
        var departmentDto = await departmentAppService.GetDepartmentById(2);

        // Arrange
        Assert.Equal("123", departmentDto.DepartmentCode);
    

【讨论】:

语法错误:不知道为什么删除了 cmets,这行给我一个错误,需要修复 // services = new Startup(configuration).ConfigureServices(services);需要主机环境名称 需求问题:另外需要将配置生成器设置为“C:\\Test\\Test.WebAPI”上方有问题的项目目录,不是当前测试目录,新的 ConfigurationBuilder() .SetBasePath(Directory .GetCurrentDirectory()) .AddJsonFile("appsettings.json", false, true));配置 = builder.Build(); 您可以从项目目录通过反射加载appsetting.josn文件。 好的会研究一下,花了很多时间研究这个问题,也随时更新答案,谢谢 hi @MohsenEsmailpour 正如你所建议的那样,我认为我们应该保留“通过测试夹具使用 DI”部分,并删除第二部分“通过 Startup.cs 使用 DI”,除非第二部分有任何好处自定义 Web 应用工厂,我认为 Web 应用工厂是 MS 推荐的方式,加上你向我推荐了它!我会在几天内给您发送赏金积分,谢谢!已经竖起大拇指了【参考方案2】:

使用自定义 Web 应用程序工厂和下面的ServiceProvider.GetRequiredService,随时编辑和优化答案

CustomWebApplicationFactory:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    
        builder.ConfigureAppConfiguration((hostingContext, configurationBuilder) =>
        
            var type = typeof(TStartup);
            var path = @"C:\\OriginalApplication";

            configurationBuilder.AddJsonFile($"path\\appsettings.json", optional: true, reloadOnChange: true);
            configurationBuilder.AddEnvironmentVariables();
        );

        // if you want to override Physical database with in-memory database
        builder.ConfigureServices(services =>
        
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            services.AddDbContext<ApplicationDBContext>(options =>
            
                options.UseInMemoryDatabase("DBInMemoryTest");
                options.UseInternalServiceProvider(serviceProvider);
            );
        );
    

集成测试:

public class DepartmentAppServiceTest : IClassFixture<CustomWebApplicationFactory<OriginalApplication.Startup>>

    public CustomWebApplicationFactory<OriginalApplication.Startup> _factory;
    public DepartmentAppServiceTest(CustomWebApplicationFactory<OriginalApplication.Startup> factory)
    
        _factory = factory;
        _factory.CreateClient();
    

    [Fact]
    public async Task ValidateDepartmentAppService()
          
        using (var scope = _factory.Server.Host.Services.CreateScope())
        
            var departmentAppService = scope.ServiceProvider.GetRequiredService<IDepartmentAppService>();
            var dbtest = scope.ServiceProvider.GetRequiredService<ApplicationDBContext>();
            dbtest.Department.Add(new Department  DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" );
            dbtest.SaveChanges();
            var departmentDto = await departmentAppService.GetDepartmentById(2);
            Assert.Equal("123", departmentDto.DepartmentCode);
        
    

资源:

https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2

https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api

【讨论】:

请注意,这会在 .Net core 3+ 上抛出 TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available.【参考方案3】:

当您进行测试时。您需要使用模拟库或直接在构造函数上注入您的服务。

public DBContext context;
public IDepartmentAppService departmentAppService;

/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)

    this.departmentAppService = departmentAppService;

【讨论】:

我无法使用依赖注入进行测试?我可以在实际应用程序中使用依赖注入,但不能使用 Xunit?不想使用模拟库,这个集成测试不是单元测试 默认 DI 库使用 Startup.cs,您不能在测试类中注入默认 DI。使用您的测试服务器 API 端点或在构造函数 @AlanWalker 上注入您的服务 你不能直接测试。您可以测试 DepartmentAppService 使用的控制器。您应该创建客户端以在您的测试服务器上请求@AlanWalker 好吧,我需要测试存储库、应用程序层、api 层,所以控制器测试可能不仅可以工作,但是谢谢,令人失望的是微软和 Xunit 还没有为此创建库,我有一个完整的列表我需要运行的依赖注入 没有办法可以用变量名替换 DepartmentAppService 吗?在启动时声明后,我不想再次引用它

以上是关于Net Core:在Xunit Test中对AppService、Repository等执行所有依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

json 适用于.NET Core的xUnit测试项目

无法使用 .NET Core 运行 xUnit 测试

无法使用 .NET Core 运行 xUnit 测试

如何在 xUnit 测试项目(.NET Core)中完成集成测试后关闭 Resharper 测试运行器?

GitHub的dotnet core CI实践(.net core + xUnit + OpenCover + Appveyor + Coveralls.net)

.Net Core 5 Web Api - Xunit 没有读取我的 appsettings.Development