【问题标题】:Is it possible to use Dependency Injection with xUnit?是否可以将依赖注入与 xUnit 一起使用?
【发布时间】:2017-01-01 01:20:25
【问题描述】:

我有一个测试类,它有一个需要 IService 的构造函数。

public class ConsumerTests
{
    private readonly IService _service;
    public ConsumerTests(IService servie)
    {
      _service = service;
    }

    [Fact]
    public void Should_()
    {
       //use _service
    }
}

我想插入我选择的 DI 容器来构建测试类

xUnit 可以做到这一点吗?

【问题讨论】:

  • 您找到解决方案了吗?我也有同样的问题。我的 xUnit 测试中有很多依赖项,手动实例化 30 个依赖项不是一个合适的解决方案。
  • 嗨 @MohammedNoureldin 更新了下面的解决方案
  • 这个开源项目可以帮助利用微软在 Xunit 中的 DI:github.com/Umplify/xunit-dependency-injection
  • 试试这个 xunit 框架内置的 xunit di 支持:nuget.org/packages/Xunit.Di

标签: unit-testing testing dependency-injection ioc-container xunit


【解决方案1】:

是的,可以使用 Xunit.DependencyInjection

Install-Package Xunit.DependencyInjection

你可以注入你的服务

[assembly: TestFramework("Your.Test.Project.Startup", "AssemblyName")]
    
namespace Your.Test.Project
{
    public class Startup : DependencyInjectionTestFramework
    {
        public Startup(IMessageSink messageSink) : base(messageSink) { }
    
        protected override void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDependency, DependencyClass>();
        }
    }
}

https://github.com/pengweiqhca/Xunit.DependencyInjection

【讨论】:

  • 请注意,这显示了 v5 是如何做到的。最新版本(截至撰写本文时)的做法略有不同。立即查看 github 上的链接文档。
【解决方案2】:

是的,现在有,我认为这两个问题和答案应该合并,在这里查看答案

Net Core: Execute All Dependency Injection in Xunit Test for AppService, Repository, etc

使用下面的自定义 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

【讨论】:

  • 哇,我还没试过,但我会投票给它,因为它似乎是正确的答案。
【解决方案3】:

有一种方法可以使用此源代码中的 nuget 包来执行此操作:https://github.com/dennisroche/xunit.ioc.autofac

只要您使用[Fact],它就可以很好地工作,但是当我开始使用[Theory] 时,我被阻止了。有一个拉取请求来解决这个问题。

为了解除阻塞,我使用 CollectionFixture 注入 Container 并从容器中解析接口。

【讨论】:

    【解决方案4】:

    你想测试什么? IService的实现还是DI容器的布线?

    如果您正在测试 IService 实现,您应该在测试中直接实例化它们(并模拟任何依赖项):

    var service = new MyServiceImplementation(mockDependency1, mockDependency2, ...);
    // execute service and do your asserts, probably checking mocks
    

    如果您正在尝试测试 DI 容器的接线,则需要伸手并显式抓取已配置的容器。没有“组合根”可以为您做到这一点(伪代码如下,有点 Autofac 风格):

    var myContainer = myCompositionRoot.GetContainer();
    var service = myContainer.ResolveCompnent<IService>();
    // execute service and do your asserts with the actual implementation
    

    如果您使用 xUnit 运行集成测试,而您需要在多个测试中使用同一个对象,请查看 Fixtures:https://xunit.net/docs/shared-context

    【讨论】:

      【解决方案5】:

      还有其他答案更适合您的问题,但我想展示我们如何在没有 IOC 容器的情况下使用 TheoryData 做到这一点。

      观看: 这是我的界面

      public interface IEpisodeManager
      {
          Task<bool> Update(Episode episode);
          Task<bool> Set(IDictionary<string, IList<Episode>> creatorsToEpisodes);
          Task<Episode> GetById(string creatorId, int episodeId);
          Task<IEnumerable<Episode>> GetEpisodesByCreatorId(string creatorId);
      }
      

      敬请期待,这是测试课:

      1. 我们在这里所做的是使用接口作为测试的输入参数。

      2. 然后创建一个 TheoryData 对象。这可以像列表一样初始化。如果您有依赖项并且不希望嵌套构造函数调用,只需将其包装在一个属性中即可。

      3. 在您的测试方法上放置一个 Theory 和 MemberData 属性。

        公共类 EpisodeManagerTests { 公共静态 TheoryData EpisodeManager = 新的理论数据() { 新的 CreatorToKeysEpisodeManager(), 新的 CreatorToEpisodeManager() };

         public EpisodeManagerTests()
         {
         }
        
         [Theory]
         [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
         public async Task GetById_ResponseProperlyMapped(IEpisodeManager manager)
         {
             var dict = EpisodeMock.CreatorToEpisodes;
             var setResult = await manager.Set(dict);
             Assert.True(setResult);
        
             var getResult = await manager.GetById("creator1", 2);
             Assert.NotNull(getResult);
         }
        
         [Theory]
         [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
         public async Task GetEpisodesByCreatorId_ResponseProperSize(IEpisodeManager manager)
         {
             var dict = EpisodeMock.CreatorToEpisodes;
             var setResult = await manager.Set(dict);
             Assert.True(setResult);
        
             var getResult = await manager.GetEpisodesByCreatorId("creator1");
             Assert.True(getResult.Count() == 4);
         }
        
         [Theory]
         [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
         public async Task GetEpisodesId_ResponseProperlyMapped(IEpisodeManager manager)
         {
             var dict = EpisodeMock.CreatorToEpisodes;
             var setResult = await manager.Set(dict);
             Assert.True(setResult);
        
             var getResult = await manager.GetById("creator1", 1);
             Assert.Equal(1, getResult.Id);
             Assert.Equal("creator1", getResult.CreatorId);
             Assert.Equal("filepath", getResult.FilePath);
             Assert.Equal("Casablanca", getResult.Name);
             Assert.Equal<TimeSpan>(TimeSpan.FromHours(4.99), getResult.RunningTime);
             Assert.Equal(DateTime.Parse("6/25/21"), getResult.AiredDate);
         }
        
         [Theory]
         [MemberData(nameof(EpisodeManager), MemberType = typeof(EpisodeManagerTests))]
         public async Task Update_ResponseProperlyMapped(IEpisodeManager manager)
         {
             var dict = EpisodeMock.CreatorToEpisodes;
             var setResult = await manager.Set(dict);
             Assert.True(setResult);
        
             var updateRequest = new Episode()
             {
                 AiredDate = DateTime.Parse("6/25/21"),
                 CreatorId = "creator1",
                 FilePath = "filepath",
                 Id = 1,
                 Name = "Casablanca: The origin story",
                 RunningTime = TimeSpan.FromHours(4.99)
             };
        
             var updateResult = await manager.Update(updateRequest);
        
             var getResult = await manager.GetById("creator1", 1);
             Assert.Equal(1, getResult.Id);
             Assert.Equal("creator1", getResult.CreatorId);
             Assert.Equal("filepath", getResult.FilePath);
             Assert.Equal("Casablanca: The origin story", getResult.Name);
             Assert.Equal<TimeSpan>(TimeSpan.FromHours(4.99), getResult.RunningTime);
             Assert.Equal(DateTime.Parse("6/25/21"), getResult.AiredDate);
         }
        

        }

      这将为您的接口的各种实现编写测试。

      如果您有多层依赖项,这可能不实用,但如果项目足够简单,这是一个超级简单的方法。

      【讨论】:

        【解决方案6】:

        xunit.di 是 xUnit 测试框架的扩展,为支持 xUnit 依赖注入而构建,它允许我们在测试类及其依赖项之间实现控制反转(IoC)。

        Install-Package Xunit.Di
        

        使用 xunit.di:

        • 安装 xunit.di nuget 包
        • 创建一个 Setup.cs 类,(可选)并继承 Xunit.Di.Setup.cs
        • 在 Setup.cs 类中配置依赖项。

        查找来自xunit.di GET-STARTED的说明

        你需要一个配置IServiceProviderSetup.cs类,

            public class Setup
            {
                private readonly IHostBuilder _defaultBuilder;
                private IServiceProvider _services;
                private bool _built = false;
        
                public Setup()
                {
                    _defaultBuilder = Host.CreateDefaultBuilder();
                }
        
                public IServiceProvider Services => _services ?? Build();
        
                private IServiceProvider Build()
                {
                    if (_built)
                        throw new InvalidOperationException("Build can only be called once.");
                    _built = true;
        
                    _defaultBuilder.ConfigureServices((context, services) =>
                    {
                        services.AddSingleton<IService, ServiceImpl>();
                        // where ServiceImpl implements IService
                        // ... add other services when needed
                    });
        
                    _services = _defaultBuilder.Build().Services;
                    return _services;
                }
            }
        

        您的测试类如下所示,

            public class ConsumerTests
            {
                private readonly IService _service;
                public ConsumerTests(IService servie)
                {
                    _service = service;
                }
        
                [Fact]
                public void Should_()
                {
                    var result = _service.GetById("1");
                    Assert.NotNull(result);
                    //use _service
                }
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-03-18
          • 2021-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-14
          • 2012-03-14
          • 1970-01-01
          相关资源
          最近更新 更多