Net Core : AppService, Repository 등에 대한 Xunit 테스트에서 모든 종속성 주입 실행

Aug 03 2019

AppService에 대한 Xunit 테스트에서 종속성 주입을 구현하려고합니다. 이상적인 목표는 원래 응용 프로그램 시작 / 구성을 실행하고 내 테스트에서 모든 DI를 다시 초기화하는 대신 시작에 있던 종속성 주입을 사용하는 것입니다. 이것이 전체 목표입니다.

업데이트 : Mohsen의 대답은 가깝습니다. 작동하려면 몇 가지 구문 / 요구 사항 오류를 업데이트해야합니다.

어떤 이유로 원래 응용 프로그램이 작동하고 Department App Service를 호출 할 수 있습니다. 단, Xunit에서는 호출 할 수 없습니다. 마지막으로 원래 응용 프로그램의 시작 및 구성을 사용하여 Testserver가 작동했습니다. 이제 아래 오류가 발생합니다.

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에는 Startup 클래스에도 삽입되는 IDepartmentRepository에 대한 종속성과 Automapper 종속성이 있습니다. 이것이 전체 시작 클래스를 호출하는 이유입니다.

좋은 자원 :

생성자 종속성 주입을 사용하여 asp.net 핵심 애플리케이션을 단위 테스트하는 방법

Xunit 프로젝트의 의존성 주입

답변

14 MohsenEsmailpour Aug 04 2019 at 13:20

단위 테스트와 통합 테스트를 혼합하고 있습니다. TestServer통합 테스트를위한 Startup것이며 등록 종속성을 다시 피하기 위해 클래스 를 재사용 하려면을 사용 HttpClient하는 컨트롤러 및 동작을 사용하고 HTTP 호출을 만들어야합니다 IDepartmentAppService.

단위 테스트를하려면 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클래스, 변화 ConfigureServicesStartup클래스를 :

public IServiceCollection ConfigureServices(IServiceCollection services)
{
     .
     .
     .
     return services;

테스트 프로젝트 재사용 IocConfig또는 Startup클래스에서 :

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);
    }
}
12 Noname Aug 07 2019 at 11:48

Custom Web Application Factory 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

1 YigitTanriverdi Aug 03 2019 at 01:04

테스트 할 때. 모의 라이브러리를 사용하거나 생성자에 직접 서비스를 주입해야합니다.

public DBContext context;
public IDepartmentAppService departmentAppService;

/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
{
    this.departmentAppService = departmentAppService;
}