La configuration .NET Core permet à tant d'options d'ajouter des valeurs (variables d'environnement, fichiers json, arguments de ligne de commande).
Je ne peux tout simplement pas comprendre et trouver une réponse comment le remplir via le code.
J'écris des tests unitaires pour les méthodes d'extension aux configurations et je pensais que les remplir dans les tests unitaires via du code serait plus facile que de charger des fichiers json dédiés pour chaque test.
Mon code actuel:
[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());
}
Mettre à jour:
Un cas particulier est la "section vide" qui ressemblerait à ceci dans json.
{
"MySection": {
// the existence of the section activates something triggering IsConfigured to be true but does not overwrite any default value
}
}
Mise à jour 2:
Comme Matthew l'a souligné dans les commentaires, avoir une section vide dans le json donne le même résultat que de ne pas avoir la section du tout. J'ai distillé un exemple et oui, c'est le cas. J'avais tort de m'attendre à un comportement différent.
Alors qu'est-ce que je fais et à quoi je m'attendais:
J'écris des tests unitaires pour 2 méthodes d'extension pour IConfiguration (en fait parce que la liaison des valeurs dans la méthode Get ... Settings ne fonctionne pas pour une raison quelconque (mais c'est un sujet différent). Ils ressemblent à ceci:
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;
}
Mon malentendu était que si je plaçais une section vide dans les paramètres d'application, la méthode IsService1Configured()
retournerait true
(ce qui est manifestement faux maintenant). La différence que j'attendais est d'avoir une section vide maintenant la méthode GetService1Settings()
retourne null
et non pas comme je m'y attendais le MyService1Settings
Avec toutes les valeurs par défaut.
Heureusement, cela fonctionne toujours pour moi car je n'aurai pas de sections vides (ou maintenant je sais que je dois éviter ces cas). Ce n'est qu'un cas théorique que j'ai rencontré lors de l'écriture des tests unitaires.
Plus loin sur la route (pour ceux qui sont intéressés).
Pour quoi l'utilise-je? Activation/désactivation du service basé sur la configuration.
J'ai une application qui contient un service/certains services. Selon le déploiement, j'ai besoin d'activer/désactiver complètement les services. En effet, certains (configurations locales ou tests) n'ont pas un accès complet à une infrastructure complète (services d'assistance comme la mise en cache, les métriques ...). Et je le fais via les applications. Si le service est configuré (la section de configuration existe), il sera ajouté. Si la section de configuration n'est pas présente, elle ne sera pas utilisée.
Le code complet de l'exemple distillé est ci-dessous.
Service1
et 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)));
}
}
}
Est-ce que AddInMemoryCollection
la méthode d'extension aiderait?
Vous pouvez y passer une collection de valeurs-clés: IEnumerable<KeyValuePair<String,String>>
avec les données dont vous pourriez avoir besoin pour un test.
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(new Dictionary<string, string>
{
{ "key", "value" }
});
La solution que j'ai choisie (qui répond au moins au titre de la question!) Consiste à utiliser un fichier de paramètres dans la solution testsettings.json
et définissez-le sur "Copier toujours".
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;
}
}
Vous pouvez utiliser la technique suivante pour simuler la méthode d'extension 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);