我有一个C#单元测试项目,其中包含app.config
文件中的应用程序设置。我正在测试一个存在于不同项目中的类。该课程取决于ConfigurationManager.AppSettings
和ConfigurationManager.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"];
答案 0 :(得分:11)
考虑重构访问配置的代码以使用包装器。然后你可以为包装类编写模拟,而不必处理导入测试的配置文件。
在两者共有的库中,有这样的东西:
public interface IConfigurationWrapper {
string GetValue(string key);
bool HasKey(string key);
}
然后,在需要访问config的库中,将此接口类型的实例注入需要读取config的类中。
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的假版本并将其传递给您进行测试,您可以在其中对GetValue
和HasKey
函数的返回值进行硬编码,或者如果您需要使用像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();
这篇文章涵盖了这个概念(虽然对于网络表单,但概念是相同的):http://www.schwammysays.net/how-to-unit-test-code-that-uses-appsettings-from-web-config/
答案 1 :(得分:7)
您提到了项目属性中的设置。看看你是否可以这样访问设置:
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>
答案 2 :(得分:0)
然后他尖叫“ NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO”。
引用:我有一个C#单元测试项目,其中app.config文件中有应用程序设置。我正在测试存在于其他项目中的类。该类依赖于ConfigurationManager.AppSettings和ConfigurationManager.ConnectionStrings。
您不这样做。永远!!!为什么?因为您现在已经创建了一个依赖项。而是使用依赖注入,以便类可以完成其工作,而不必深入研究属于该应用程序的配置文件。
答案 3 :(得分:0)
如果您使用的是.NET Core ,则可能是由于测试过程以testhost.dll(或testhost.x86.dll)运行而引起的issue问题,这意味着运行时配置文件的名称应为“ testhost.dll.config”,而不是实际名称“ MyLibrary.Tests.dll.config”。
要修复此问题,请在根节点<Project>
内,将以下代码添加到您的项目文件(.csproj等)中。在构建过程中,两个app.config副本将被放置在输出目录中,分别名为“ testhost.dll.config”和“ testhost.x86.dll.config”,这将使您的应用程序设置再次起作用。 (您只需要这些文件中的一个,但可以安全地包含两个文件。)
<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提供的用于存储应用程序设置的新的,更优雅的解决方案。