it-swarm-pt.tech

Configuração IC de preenchimento para testes de unidade

A configuração do .NET Core permite que muitas opções adicionem valores (variáveis ​​de ambiente, arquivos json, argumentos da linha de comando).

Eu simplesmente não consigo descobrir e encontrar uma resposta sobre como preenchê-lo via código.

Estou escrevendo testes de unidade para métodos de extensão de configurações e pensei que preenchê-lo nos testes de unidade via código seria mais fácil do que carregar arquivos json dedicados para cada teste.

Meu código atual:

  [Fact]
  public void Test_IsConfigured_Positive()
  {

    // test against this configuration
    IConfiguration config = new ConfigurationBuilder()
      // how to populate it via code
      .Build();

    // the extension method to test
    Assert.True(config.IsConfigured());

  }

Atualizar:

Um caso especial é a "seção vazia", ​​que ficaria assim em json.

  {
    "MySection": {
       // the existence of the section activates something triggering IsConfigured to be true but does not overwrite any default value
     }
   }

Atualização 2:

Como Matthew apontou nos comentários, ter uma seção vazia no json dá o mesmo resultado de não ter a seção. Eu destilou um exemplo e sim, é esse o caso. Eu estava errado esperando um comportamento diferente.

Então, o que eu faço e o que eu esperava:

Estou escrevendo testes de unidade para 2 métodos de extensão para IConfiguration (na verdade, porque a ligação de valores no método Get ... Settings não funciona por algum motivo (mas é um tópico diferente).

  public static bool IsService1Configured(this IConfiguration configuration)
  {
    return configuration.GetSection("Service1").Exists();
  }

  public static MyService1Settings GetService1Settings(this IConfiguration configuration)
  {
    if (!configuration.IsService1Configured()) return null;

    MyService1Settings settings = new MyService1Settings();
    configuration.Bind("Service1", settings);

    return settings;
  }

Meu entendimento equivocado era que, se eu colocasse uma seção vazia nas configurações de aplicativos, o método IsService1Configured() retornaria true (que está obviamente errado agora). A diferença que eu esperava é ter uma seção vazia agora que o método GetService1Settings() retorna null e não como eu esperava o MyService1Settings Com todos os valores padrão.

Felizmente, isso ainda funciona para mim, pois não terei seções vazias (ou agora sei que preciso evitar esses casos). Foi apenas um caso teórico que encontrei enquanto escrevia os testes de unidade.

Mais adiante (para os interessados).

Para o que eu uso? Ativação/desativação de serviço baseado em configuração.

Eu tenho um aplicativo que possui um serviço/alguns serviços compilados nele. Dependendo da implantação, preciso ativar/desativar os serviços completamente. Isso ocorre porque algumas (configurações locais ou de teste) não têm acesso total a uma infraestrutura completa (serviços auxiliares, como cache, métricas ...). E faço isso através das configurações de aplicativos. Se o serviço estiver configurado (a seção de configuração existe), ele será adicionado. Se a seção de configuração não estiver presente, ela não será usada.


O código completo do exemplo destilado está abaixo.

  • no Visual Studio, crie uma nova API chamada WebApplication1 a partir dos modelos (sem HTTPS e autenticação)
  • exclua a classe Startup e appsettings.Development.json
  • substitua o código em Program.cs pelo código abaixo
  • agora em appsettings.json, você pode ativar/desativar os serviços adicionando/removendo as seções Service1 e Service2
  using Microsoft.AspNetCore;
  using Microsoft.AspNetCore.Builder;
  using Microsoft.AspNetCore.Hosting;
  using Microsoft.AspNetCore.Mvc;
  using Microsoft.Extensions.Configuration;
  using Microsoft.Extensions.DependencyInjection;
  using Microsoft.Extensions.Logging;
  using Newtonsoft.Json;
  using System;

  namespace WebApplication1
  {

    public class MyService1Settings
    {
    public int? Value1 { get; set; }
    public int Value2 { get; set; }
    public int Value3 { get; set; } = -1;
    }

    public static class Service1Extensions
    {

    public static bool IsService1Configured(this IConfiguration configuration)
    {
    return configuration.GetSection("Service1").Exists();
    }

    public static MyService1Settings GetService1Settings(this IConfiguration configuration)
    {
    if (!configuration.IsService1Configured()) return null;

    MyService1Settings settings = new MyService1Settings();
    configuration.Bind("Service1", settings);

    return settings;
    }

    public static IServiceCollection AddService1(this IServiceCollection services, IConfiguration configuration, ILogger logger)
    {

    MyService1Settings settings = configuration.GetService1Settings();

    if (settings == null) throw new Exception("loaded MyService1Settings are null (did you forget to check IsConfigured in Startup.ConfigureServices?) ");

    logger.LogAsJson(settings, "MyServiceSettings1: ");

    // do what ever needs to be done

    return services;
    }

    public static IApplicationBuilder UseService1(this IApplicationBuilder app, IConfiguration configuration, ILogger logger)
    {

    // do what ever needs to be done

    return app;
    }

    }

    public class Program
    {

      public static void Main(string[] args)
      {
        CreateWebHostBuilder(args).Build().Run();
      }

      public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .ConfigureLogging
          (
          builder => 
            {
              builder.AddDebug();
              builder.AddConsole();
            }
          )
        .UseStartup<Startup>();
        }

      public class Startup
      {

        public IConfiguration Configuration { get; }
        public ILogger<Startup> Logger { get; }

        public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
        {
        Configuration = configuration;
        Logger = loggerFactory.CreateLogger<Startup>();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

        // flavour 1: needs check(s) in Startup method(s) or will raise an exception
        if (Configuration.IsService1Configured()) {
        Logger.LogInformation("service 1 is activated and added");
        services.AddService1(Configuration, Logger);
        } else 
        Logger.LogInformation("service 1 is deactivated and not added");

        // flavour 2: checks are done in the extension methods and no Startup cluttering
        services.AddOptionalService2(Configuration, Logger);

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
      }

      // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
      public void Configure(IApplicationBuilder app, IHostingEnvironment env)
      {

        if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

        // flavour 1: needs check(s) in Startup method(s) or will raise an exception
        if (Configuration.IsService1Configured()) {
          Logger.LogInformation("service 1 is activated and used");
          app.UseService1(Configuration, Logger); }
        else
          Logger.LogInformation("service 1 is deactivated and not used");

        // flavour 2: checks are done in the extension methods and no Startup cluttering
        app.UseOptionalService2(Configuration, Logger);

        app.UseMvc();
      }
    }

    public class MyService2Settings
    {
      public int? Value1 { get; set; }
      public int Value2 { get; set; }
      public int Value3 { get; set; } = -1;
    }

    public static class Service2Extensions
    {

    public static bool IsService2Configured(this IConfiguration configuration)
    {
      return configuration.GetSection("Service2").Exists();
    }

    public static MyService2Settings GetService2Settings(this IConfiguration configuration)
    {
      if (!configuration.IsService2Configured()) return null;

      MyService2Settings settings = new MyService2Settings();
      configuration.Bind("Service2", settings);

      return settings;
    }

    public static IServiceCollection AddOptionalService2(this IServiceCollection services, IConfiguration configuration, ILogger logger)
    {

      if (!configuration.IsService2Configured())
      {
        logger.LogInformation("service 2 is deactivated and not added");
        return services;
      }

      logger.LogInformation("service 2 is activated and added");

      MyService2Settings settings = configuration.GetService2Settings();
      if (settings == null) throw new Exception("some settings loading bug occured");

      logger.LogAsJson(settings, "MyService2Settings: ");
      // do what ever needs to be done
      return services;
    }

    public static IApplicationBuilder UseOptionalService2(this IApplicationBuilder app, IConfiguration configuration, ILogger logger)
    {

      if (!configuration.IsService2Configured())
      {
        logger.LogInformation("service 2 is deactivated and not used");
        return app;
      }

      logger.LogInformation("service 2 is activated and used");
      // do what ever needs to be done
      return app;
    }
  }

    public static class LoggerExtensions
    {
      public static void LogAsJson(this ILogger logger, object obj, string prefix = null)
      {
        logger.LogInformation(prefix ?? string.Empty) + ((obj == null) ? "null" : JsonConvert.SerializeObject(obj, Formatting.Indented)));
      }
    }

  }
5
monty

O método de extensão AddInMemoryCollection ajudaria?

Você pode passar uma coleção de valores-chave para ela: IEnumerable<KeyValuePair<String,String>> com os dados que você pode precisar para um teste.

var builder = new ConfigurationBuilder();

builder.AddInMemoryCollection(new Dictionary<string, string>
{
     { "key", "value" }
});
1
Anton Sizikov

A solução que eu procurei (que responde pelo menos o título da pergunta!) É usar um arquivo de configurações na solução testsettings.json e defina-o como "Copiar sempre".

    private IConfiguration _config;

    public UnitTestManager()
    {
        IServiceCollection services = new ServiceCollection();

        services.AddSingleton<IConfiguration>(Configuration);
    }

    public IConfiguration Configuration
    {
        get
        {
            if (_config == null)
            {
                var builder = new ConfigurationBuilder().AddJsonFile($"testsettings.json", optional: false);
                _config = builder.Build();
            }

            return _config;
        }
    }
0
noelicus

Você pode usar a seguinte técnica para zombar do método de extensão IConfiguration.GetValue<T>(key).

var configuration = new Mock<IConfiguration>();
var configSection = new Mock<IConfigurationSection>();

configSection.Setup(x => x.Value).Returns("fake value");
configuration.Setup(x => x.GetSection("MySection")).Returns(configSection.Object);
//OR
configuration.Setup(x => x.GetSection("MySection:Value")).Returns(configSection.Object);
0
Serj