c# - صافي كور: تنفيذ جميع حقن التبعية في اختبار Xunit ل AppService ، مستودع ، الخ




.net asp.net-core (2)

أنت تخلط اختبار الوحدة مع اختبار التكامل. 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 الخاصة بك في فصل آخر واستخدام تلك الفئة في أي مكان تريد.

باستخدام DI خلال 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;
    }
}

في فئة 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 ، IocConfig بتغيير ConfigureServices في فئة Startup :

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

أحاول تنفيذ Dependency Injection في اختبار Xunit لـ AppService. الهدف المثالي هو تشغيل برنامج التطبيق الأصلي / بدء التشغيل ، واستخدام أي حقن التبعية الذي كان في بدء التشغيل ، بدلا من إعادة تهيئة جميع DI مرة أخرى في اختبار بلدي ، وهذا هو الهدف كله في السؤال.

تحديث: الجواب محسن قريب. تحتاج إلى تحديث أخطاء بناء جملة / متطلبات للعمل.

لسبب ما ، يعمل التطبيق الأصلي ويمكنه الاتصال بخدمة تطبيقات الدائرة ومع ذلك ، فإنه لا يمكن استدعاء في 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 على IDepartmentRepository الذي يتم حقنه أيضًا في فئة بدء التشغيل ، وتبعيات Automapper. هذا هو السبب في استدعاء فئة بدء التشغيل بأكملها.

موارد جيدة:

كيفية وحدة اختبار التطبيق asp.net الأساسية مع حقن التبعية منشئ

حقن التبعية في مشروع Xunit


استخدم 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





xunit