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

后端 未结 3 1493
暖寄归人
暖寄归人 2020-11-28 11:37

I am trying to implement Dependency Injection in Xunit test for AppService. Ideal goal is to run the original application program Startup/configuration, and use any depende

相关标签:
3条回答
  • 2020-11-28 11:41

    When you are testing. You need to use mocking libraries or Inject your service directly on contructor ie.

    public DBContext context;
    public IDepartmentAppService departmentAppService;
    
    /// Inject DepartmentAppService here
    public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
    {
        this.departmentAppService = departmentAppService;
    }
    
    0 讨论(0)
  • 2020-11-28 11:50

    Use Custom Web Application Factory and ServiceProvider.GetRequiredService below, feel free to edit and optimize the answer

    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);
                });
            });
        }
    }
    

    Integration Test:

    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);
            }
        }
    }
    

    Resources:

    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

    0 讨论(0)
  • 2020-11-28 11:53

    You are mixing unit test with integration test. TestServer is for integration test and if you want to reuse Startup class to avoid register dependencies again, you should use HttpClient and make HTTP call to controller and action that use IDepartmentAppService.

    If you want do unit test, you need to setup DI and register all needed dependencies to test IDepartmentAppService.

    Using DI through Test Fixture:

    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);           
            }
        }
    }
    

    Using dependency injection with unit test is not good idea and you should avoid that. by the way if you want don't repeat your self for registering dependencies, you can wrap your DI configuration in another class and use that class anywhere you want.

    Using DI through Startup.cs:

    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;
        }
    }
    

    in Startup class and ConfigureServices method just useIocConfig class:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        public void ConfigureServices(IServiceCollection services)
        {
             IocConfig.Configure(services, configuration);
    
             services.AddMvc();
             .
             .
             .
    

    if you don't want use IocConfig class, change ConfigureServices in Startup class:

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

    and in test project reuse IocConfig or Startup class:

    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; }
    }
    

    and in test method:

    [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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题