gpt4 book ai didi

asp.net-core - 填充 IConfiguration 以进行单元测试

转载 作者:行者123 更新时间:2023-12-02 05:37:06 24 4
gpt4 key购买 nike

.NET Core 配置允许使用很多选项来添加值(环境变量、json 文件、命令行参数)。

我只是无法弄清楚并找到如何通过代码填充它的答案。

我正在为配置的扩展方法编写单元测试,我认为通过代码将其填充到单元测试中比为每个测试加载专用的 json 文件更容易。

我当前的代码:

[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());

}

更新:

一个特殊情况是“空部分”,在 json 中看起来像这样。

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

更新2:

正如 Matthew 在评论中指出的那样,json 中包含空部分与根本没有该部分的结果相同。我提炼了一个例子,是的,就是这样。我错误地期待着不同的行为。

那么我该怎么做以及我期望什么:

我正在为 IConfiguration 的 2 个扩展方法编写单元测试(实际上是因为 Get...Settings 方法中的值绑定(bind)由于某种原因不起作用(但那是另一个主题)。它们看起来像这样:

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

我的误解是,如果我在应用程序设置中放置一个空部分,IsService1Configured() 方法将返回 true (现在这显然是错误的)。我期望的不同之处在于,现在 GetService1Settings() 方法返回 null 为空部分,而不是像我期望的那样返回具有所有默认值的 MyService1Settings .

幸运的是,这仍然对我有用,因为我不会有空的部分(或者现在知道我必须避免这些情况)。这只是我在编写单元测试时遇到的一个理论案例。

进一步发展(对于那些感兴趣的人)。

我用它来做什么?基于配置的服务激活/停用。

我有一个应用程序,其中编译了一个服务/一些服务。根据部署,我需要完全激活/停用服务。这是因为某些(本地或测试设置)无法完全访问完整的基础设施(缓存、指标等帮助服务)。我通过应用程序设置来做到这一点。如果服务已配置(配置部分存在),则会添加该服务。如果配置部分不存在,则不会使用它。

<小时/>

精简示例的完整代码如下。

  • 在 Visual Studio 中从模板创建一个名为 WebApplication1 的新 API(无需 HTTPS 和身份验证)
  • 删除 Startup 类和 appsettings.Development.json
  • 将 Program.cs 中的代码替换为以下代码
  • 现在,在 appsettings.json 中,您可以通过添加/删除 Service1Service2 部分来激活/停用服务
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)));
}
}

}

最佳答案

您可以使用MemoryConfigurationBuilderExtensions通过字典提供它。

using Microsoft.Extensions.Configuration;

var myConfiguration = new Dictionary<string, string>
{
{"Key1", "Value1"},
{"Nested:Key1", "NestedValue1"},
{"Nested:Key2", "NestedValue2"}
};

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(myConfiguration)
.Build();

等效的 JSON 为:

{
"Key1": "Value1",
"Nested": {
"Key1": "NestedValue1",
"Key2": "NestedValue2"
}
}

等效的环境变量是(假设没有前缀/不区分大小写):

Key1=Value1
Nested__Key1=NestedValue1
Nested__Key2=NestedValue2

等效的命令行参数是:

dotnet myapp.dll \
-- \
--Key1=Value1 \
--Nested:Key1=NestedValue1 \
--Nested:Key2=NestedValue2

关于asp.net-core - 填充 IConfiguration 以进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55497800/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com