【问题标题】:ConfigurationManager.AppSettings Returns Null In Unit Test ProjectConfigurationManager.AppSettings 在单元测试项目中返回 Null
【发布时间】:2014-08-10 01:19:31
【问题描述】:

我有一个 C# 单元测试项目,在 app.config 文件中有应用程序设置。我正在测试一个存在于不同项目中的类。该类取决于ConfigurationManager.AppSettingsConfigurationManager.ConnectionStrings

正在测试的类所在的项目没有 app.config 文件。我会认为因为该类是在单元测试项目的上下文中实例化的,所以它将使用单元测试项目的 app.config 文件。实际上,连接字符串似乎确实如此。

该类检索连接字符串没有任何问题。但是,当类尝试检索任何应用程序设置时,配置管理器总是返回null。这是怎么回事?

编辑 1

我认为尝试在测试项目中加载一些设置以查看会发生什么可能是个好主意。在调用实例化外部项目中的类的代码之前,我尝试立即在单元测试中加载设置。同样的结果,没有。我想我可以暂时从等式中排除其他项目。

这是我的配置文件的摘录:

<configSections>
  <sectionGroup name="applicationSettings"
                type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
    <section name="MyNamespace.Properties.Settings"
             type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
             requirePermission="false" />
  </sectionGroup>
</configSections>

...

<applicationSettings>
  <MyNamespace.Properties.Settings>
    <setting name="Bing_Key"
             serializeAs="String">
      <value>...</value>
    </setting>
  </MyNamespace.Properties.Settings>
</applicationSettings>

这是我尝试加载设置的方式:

string test = System.Configuration.ConfigurationManager.AppSettings["Bing_Key"];

【问题讨论】:

  • 您是否检查过 App.config 文件上的构建操作需要为 Content 并且“复制到输出目录”设置需要为“如果较新则复制”。
  • 这些东西都没有像你描述的那样设置。更改它们并没有解决问题。
  • 在您的 app.config 中,配置部分的类型和命名空间是否与 CM.AppSettings 命名空间匹配?
  • 您是否复制了appSettings部分并粘贴到单元测试项目的app.config中?
  • 您是否尝试过将常规项目的配置文件作为链接添加到测试项目中?测试项目是否有对 System.Configuration 的引用?

标签: c# visual-studio unit-testing app-config application-settings


【解决方案1】:

考虑重构访问配置的代码以使用包装器。然后您可以为包装类编写模拟,而不必为测试处理配置文件的导入。

在两者通用的库中,有这样的东西:

public interface IConfigurationWrapper {

    string GetValue(string key);
    bool HasKey(string key);
}

然后,在需要访问配置的库中,将该接口类型的实例注入需要读取配置的类中。

public class MyClassOne {
    
    private IConfigurationWrapper _configWrapper;

    public MyClassOne(IConfigurationWrapper wrapper) {
        _configWrapper = wrapper;
    } // end constructor

    public void MethodThatDependsOnConfiguration() {
        string configValue = "";
        if(_configWrapper.HasKey("MySetting")) {
            configValue = _configWrapper.GetValue("MySetting");
        }
    } // end method

} // end class MyClassOne

然后,在您的一个库中,创建一个依赖于配置文件的实现。

public class AppConfigWrapper : IConfigurationWrapper {
    
    public string GetValue(string key) {
        return ConfigurationManager.AppSettings[key];
    }

    public bool HasKey(string key) {
       return ConfigurationManager.AppSettings.AllKeys.Select((string x) => x.ToUpperInvariant()).Contains(key.ToUpperInvariant());
    }
}

然后,在调用你的类的代码中。

//Some method container
MyClassOne dataClass = new MyClassOne(new AppConfigWrapper());

dataClass.MethodThatDependsOnConfiguration();

那么在你的测试中,你就摆脱了依赖束缚。 :) 您可以创建一个实现 IConfigurationWrapper 的假版本并将其传递给您的测试,您可以在其中硬编码来自 GetValueHasKey 函数的返回值,或者如果您使用像 Moq 这样的模拟库:

Mock<IConfigurationWrapper> fakeWrapper = new Mock<IConfigurationWrapper>();

fakeWrapper.Setup((x) => x.GetValue(It.IsAny<string>)).Returns("We just bypassed config.");

MyClassOne testObject = new MyClassOne(fakeWrapper.Object);
testObject.MethodThatDependsOnConfiguration();

这里有一篇文章介绍了这个概念(虽然是针对 Web 表单,但概念是一样的):http://www.schwammysays.net/how-to-unit-test-code-that-uses-appsettings-from-web-config/

【讨论】:

    【解决方案2】:

    如果您使用的是 .NET Core,您的问题可能是已知的 issue,原因是测试进程以 testhost.dll(或 testhost.x86.dll)的形式运行,这表示运行时配置文件应命名为“testhost.dll.config”(或“testhost.x86.dll.config”),而不是 app.config 输出(例如:“MyLibrary.Tests.dll.config”) .

    要修复它,请将以下代码添加到根节点 &lt;Project&gt; 内的项目文件(.csproj 等)中。在构建期间,app.config 的两个副本将被放入输出目录并命名为“testhost.dll.config”和“testhost.x86.dll.config”,这将使您的应用程序设置再次运行。 (您只需要其中 1 个文件,但同时包含这两个文件更安全。)

    <Target Name="CopyCustomContent" AfterTargets="AfterBuild">
      <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.dll.config" />
      <Copy SourceFiles="app.config" DestinationFiles="$(OutDir)\testhost.x86.dll.config" />
    </Target>
    

    我建议 app.config 仅作为临时解决方案。如果您像我一样,可能在将 .NET Framework 项目升级到 .NET Core 时遇到了问题,需要快速修复。但不要忘记研究 .NET Core 提供的用于存储应用设置的新的、更优雅的解决方案。

    【讨论】:

    • 我们应该在 .NET Core 中使用什么来存储桌面 WPF 应用程序的应用程序设置?
    • @fatihyildizhan 我使用 appsettings.json 文件,这些文件是使用选项模式加载的。 See here 概览。
    【解决方案3】:

    然后他尖叫“NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO”。

    引用:我有一个 C# 单元测试项目,其中 app.config 文件中包含应用程序设置。我正在测试一个存在于不同项目中的类。该类同时依赖于 ConfigurationManager.AppSettings 和 ConfigurationManager.ConnectionStrings。

    你不这样做。曾经!!!!为什么?因为您现在已经创建了一个依赖项。相反,使用依赖注入,这样类就可以完成它的工作,而不必进入属于应用程序的配置文件。

    【讨论】:

      【解决方案4】:

      您在项目属性中提到了设置。看看你是否可以通过这种方式访问​​设置:

      string test = Properties.Settings.Default.Bing_Key;
      

      您可能需要获取定义项目设置文件的位置的执行程序集,但请先尝试。

      编辑

      使用 Visual Studio 的项目设置文件时,它会将内容添加到您的 app.config 中,并在 app.config 不存在时创建它。 ConfigurationManager 不能触摸这些设置!您只能使用上述静态方法获取这些特定生成的 project.settings 文件。如果你想使用 ConfigurationManager,你需要手写你的 app.config。像这样添加你的设置:

      <appSettings>
        <add key="bing_api" value="whatever"/>
      </appSettings>
      

      【讨论】:

      • 在单元测试项目中工作。但是,正在测试的外部项目中没有 Properties.Settings 类。
      • 如果我错了,请纠正我,但听起来您需要该外部项目才能连接到您的项目设置中,无论它们发生在哪里。如果是这种情况,这可能有助于跨不同程序集访问该设置文件:stackoverflow.com/questions/2548476/…
      • 如果我错了,请纠正我(我可能是),但我认为 app.config 文件有点像加载到主应用程序线程中的全局/环境容器(单元在这种情况下测试项目)。然后任何其他 dll(在这种情况下为被测系统),在读取任何配置属性时,都会从同一个全局配置容器中读取属性。
      • @johnnymumble 设置基于类型和命名空间,因此它们必须匹配
      • 这是正确的,但 IIRC 项目设置的工作方式与直接的 app.config 不同。要走这条路,放弃你的项目设置文件(首先保存你的东西)并将一个新的 app.config 添加到你的单元测试(或主 exe)项目中。然后,您将设置添加到
      猜你喜欢
      • 1970-01-01
      • 2013-08-30
      • 2018-10-02
      • 2014-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-14
      • 2020-11-02
      相关资源
      最近更新 更多