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




.net asp.net-core (2)

أحاول تنفيذ 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


أنت تخلط اختبار الوحدة مع اختبار التكامل. 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);
    }
}

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