填充IConfiguration以进行单元测试

时间:2019-04-03 14:37:59

标签: asp.net-core .net-core xunit asp.net-core-2.2 .net-core-2.2

.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方法中的值绑定由于某些原因而无法工作(但这就是一个不同的主题)。它们看起来像这样:

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

我的一个误解是,如果我在appsettings中放置一个空白部分,则IsService1Configured()方法将返回true(现在显然是错误的)。我期望的区别在于现在GetService1Settings()方法返回一个空白部分,而不是我期望的null具有所有默认值。

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

再往前走(对于那些感兴趣的人)。

我该怎么用?基于配置的服务激活/停用。

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


摘录示例的完整代码如下。

  • 在Visual Studio中从模板(没有HTTPS和身份验证)创建一个名为WebApplication1的新API
  • 删除Startup类和appsettings.Development.json
  • 用下面的代码替换Program.cs中的代码
  • 现在在appsettings.json中,您可以通过添加/删除MyService1SettingsService1部分来激活/停用服务
Service2

5 个答案:

答案 0 :(得分:3)

我不想让我的应用程序类依赖于IConfiguration。相反,我创建了一个配置类来保存配置,并带有一个可以从IConfiguration进行初始化的构造函数,如下所示:

public class WidgetProcessorConfig
{
    public int QueueLength { get; set; }
    public WidgetProcessorConfig(IConfiguration configuration)
    {
        configuration.Bind("WidgetProcessor", this);
    }
    public WidgetProcessorConfig() { }
}

然后在您的ConfigureServices中,您只需要执行以下操作:

services.AddSingleton<WidgetProcessorConfig>();
services.AddSingleton<WidgetProcessor>();

并进行测试:

var config = new WidgetProcessorConfig
{
    QueueLength = 18
};
var widgetProcessor = new WidgetProcessor(config);

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:2)

我寻求的解决方案(至少可以回答问题标题!)是在解决方案import React from "react"; import { makeStyles } from '@material-ui/core/styles'; import { Box, Typography } from '@material-ui/core'; const useStyles = makeStyles(theme => ({ root: { padding: theme.spacing(0), margin: theme.spacing(0) } })); function ArticleBody(props) { const classes = useStyles(); return ( <Box className={classes.root}> <Typography key={props.key} color={props.color ? props.color : "default"} align={props.align ? props.align : 'justify'} variant={props.variant ? props.variant : 'body1'} component="div" dangerouslySetInnerHTML={{ __html: props.content }} /> </Box> ) } export default ArticleBody 中使用设置文件,并将其设置为“始终复制”。

testsettings.json

答案 3 :(得分:1)

AddInMemoryCollection扩展方法会有帮助吗?

您可以将键值集合传递给它: IEnumerable<KeyValuePair<String,String>>包含测试所需的数据。

var builder = new ConfigurationBuilder();

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

答案 4 :(得分:1)

您可以使用以下技术来模拟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);