web-dev-qa-db-fra.com

Net Core: exécutez toutes les injections de dépendance dans le test Xunit pour AppService, Repository, etc.

J'essaie d'implémenter l'injection de dépendance dans le test Xunit pour AppService. L'objectif idéal est d'exécuter le programme d'application de démarrage/configuration d'origine, et d'utiliser toute injection de dépendance qui était dans le démarrage, au lieu de réinitialiser à nouveau tous les DI dans mon test, c'est tout l'objectif en question.

Mise à jour: la réponse de Mohsen est proche. Besoin de mettre à jour les erreurs de syntaxe/exigence de couple pour fonctionner.

Pour une raison quelconque, l'application d'origine fonctionne et peut appeler le service App du Département. Cependant, il ne peut pas appeler Xunit. Enfin, Testserver a fonctionné en utilisant le démarrage et la configuration de l'application d'origine. Réception de l'erreur ci-dessous:

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

Je reçois cette erreur:

Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService

Besoin d'utiliser l'injection de dépendance dans les tests comme une application réelle. L'application originale fait cela. Les réponses ci-dessous ne sont pas actuellement suffisantes, l'une utilise la moquerie qui n'est pas l'objectif actuel, l'autre réponse utilise le contrôleur qui contourne le but de la question.

Remarque: IDepartmentAppService a une dépendance sur IDepartmentRepository qui est également injecté dans la classe Startup et les dépendances Automapper. C'est pourquoi appeler toute la classe de démarrage.

Bonnes ressources:

comment tester unitairement l'application principale asp.net avec injection de dépendance constructeur

Injection de dépendances dans le projet Xunit

14
user11860043

Utilisez Factory Web Application Factory et ServiceProvider.GetRequiredService ci-dessous, n'hésitez pas à modifier et optimiser la réponse

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

Test d'intégration:

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

Ressources:

https://docs.Microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2

https://fullstackmark.com/post/20/pireless-integration-testing-with-aspnet-core-web-api

9
user11860043

Vous mélangez le test unitaire avec le test d'intégration. TestServer est pour le test d'intégration et si vous souhaitez réutiliser la classe Startup pour éviter à nouveau les dépendances de registre, vous devez utiliser HttpClient et effectuer un appel HTTP au contrôleur et l'action qui utilise IDepartmentAppService.

Si vous voulez faire un test unitaire, vous devez configurer DI et enregistrer toutes les dépendances nécessaires pour tester IDepartmentAppService.

tilisation de DI via le dispositif de test:

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

L'utilisation de l'injection de dépendance avec le test unitaire n'est pas une bonne idée et vous devriez l'éviter. soit dit en passant si vous ne voulez pas répéter votre auto pour l'enregistrement des dépendances, vous pouvez encapsuler votre configuration DI dans une autre classe et utiliser cette classe où vous voulez.

tilisation de DI via 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;
    }
}

dans la classe Startup et la méthode ConfigureServices utilisez simplement la classe 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();
         .
         .
         .

si vous ne voulez pas utiliser la classe IocConfig, changez ConfigureServices dans la classe Startup:

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

et dans le projet de test, réutilisez la classe IocConfig ou 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; }
}

et dans la méthode d'essai:

[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);
    }
}
7
Mohsen Esmailpour

Lorsque vous testez. Vous devez utiliser des bibliothèques de simulation ou injecter votre service directement sur le constructeur, c'est-à-dire.

public DBContext context;
public IDepartmentAppService departmentAppService;

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