我卡在这段代码上,不知道该如何进行模拟:
ConfigurationManager.AppSettings["User"];
我必须模拟 ConfigurationManager,但是我不知道该怎么做,我正在使用 Moq。
有人可以给我一些提示吗?谢谢!
我卡在这段代码上,不知道该如何进行模拟:
ConfigurationManager.AppSettings["User"];
我必须模拟 ConfigurationManager,但是我不知道该怎么做,我正在使用 Moq。
有人可以给我一些提示吗?谢谢!
我正在使用AspnetMvc4。刚才我写了:
ConfigurationManager.AppSettings["mykey"] = "myvalue";
在我的测试方法中,它表现得非常完美。
解释:测试方法在上下文中运行,其中应用程序设置来自于通常是 web.config
或 myapp.config
的文件。 ConfigurationsManager
可以访问这个应用程序全局对象并对其进行操作。
但是:如果您有一个并行运行测试的测试运行器,这不是一个好主意。
我相信一种标准的方法是使用外观模式(facade pattern)来包装配置管理器,这样你就可以得到一个松散耦合并且可控的东西。
那么你需要包装 ConfigurationManager。类似这样:
public class Configuration: IConfiguration
{
public string User
{
get
{
return ConfigurationManager.AppSettings["User"];
}
}
}
您可以从配置类中提取一个接口,然后在代码的各个地方使用该接口。然后,您只需模拟IConfiguration即可。您可能能够以几种不同的方式实现facade本身。上面我选择了仅包装单个属性的方法。您还可以获得强类型信息而不是弱类型哈希数组的附加好处。
var configurationMock = new Mock<IConfiguration>();
,为设置准备:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
- Joshua Enfield也许这不是您需要实现的内容,但您是否考虑在测试项目中使用app.config文件呢?这样,ConfigurationManager将获取您在app.config中设置的值,您就无需模拟任何东西。对于我的需要,这个解决方案非常有效,因为我从未需要测试“可变”的配置文件。
您可以使用 shims 将 AppSettings
修改为自定义的 NameValueCollection
对象。以下是一个示例,演示了如何实现此操作:
[TestMethod]
public void TestSomething()
{
using(ShimsContext.Create()) {
const string key = "key";
const string value = "value";
ShimConfigurationManager.AppSettingsGet = () =>
{
NameValueCollection nameValueCollection = new NameValueCollection();
nameValueCollection.Add(key, value);
return nameValueCollection;
};
///
// Test code here.
///
// Validation code goes here.
}
}
您可以在使用 Microsoft Fakes 隔离测试代码中了解有关 shim 和 fake 的更多信息。希望能对您有所帮助。
你有考虑过使用桩对象而不是模拟对象吗?AppSettings
属性是一个 NameValueCollection
类型:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
// Arrange
var settings = new NameValueCollection {{"User", "Otuyh"}};
var classUnderTest = new ClassUnderTest(settings);
// Act
classUnderTest.MethodUnderTest();
// Assert something...
}
}
public class ClassUnderTest
{
private readonly NameValueCollection _settings;
public ClassUnderTest(NameValueCollection settings)
{
_settings = settings;
}
public void MethodUnderTest()
{
// get the User from Settings
string user = _settings["User"];
// log
Trace.TraceInformation("User = \"{0}\"", user);
// do something else...
}
}
使用此方法的好处是实现更简单,并且在真正需要 System.Configuration 时不依赖它。
IConfiguration
中可能太高级,您可能会错过由于不良配置值解析等原因而存在的错误。另一方面,像LosManos建议的直接使用ConfigurationManager.AppSettings
太多了实现细节,更不用说它可能对其他测试产生副作用,而且在并行测试运行时无法自动同步(因为NameValueCollection
不是线程安全的)。 - Ohad Schneider我担心我需要撤回我之前所说的话。 ConfigurationManager.AppSettings 会偶尔出现异常行为,就好像它不总是立即返回刚写入的值一样。因此我们在构建机器上遇到了零散的单元测试失败情况。我不得不重写我的代码来使用包装器,在通常情况下返回 ConfigurationManager.AppSettings 中的值,在单元测试中则返回测试值。
那你考虑只设置你需要的吗?毕竟,我不想模拟 .NET, 不是吗...?
System.Configuration.ConfigurationManager.AppSettings["myKey"] = "myVal";
你可能需要事先清理AppSettings,以确保应用程序只看到你想要的内容。
IConfiguration
,从任何你想要的文件中获取它,就像这样:var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
我认为编写自己的app.config提供程序是一项简单的任务,比其他任何东西都更有用。特别是你应该避免使用任何伪造的东西,如shims等,因为一旦你使用它们,编辑和继续就不再起作用。
我使用的提供程序看起来像这样:
默认情况下,它们从App.config
获取值,但对于单元测试,我可以覆盖所有值并在每个测试中独立使用它们。
没有必要使用任何接口或一遍又一遍地实现它。我有一个实用程序dll,并在许多项目和单元测试中使用这个小助手。
public class AppConfigProvider
{
public AppConfigProvider()
{
ConnectionStrings = new ConnectionStringsProvider();
AppSettings = new AppSettingsProvider();
}
public ConnectionStringsProvider ConnectionStrings { get; private set; }
public AppSettingsProvider AppSettings { get; private set; }
}
public class ConnectionStringsProvider
{
private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get
{
string customValue;
if (_customValues.TryGetValue(key, out customValue))
{
return customValue;
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[key];
return connectionStringSettings == null ? null : connectionStringSettings.ConnectionString;
}
}
public Dictionary<string, string> CustomValues { get { return _customValues; } }
}
public class AppSettingsProvider
{
private readonly Dictionary<string, string> _customValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get
{
string customValue;
return _customValues.TryGetValue(key, out customValue) ? customValue : ConfigurationManager.AppSettings[key];
}
}
public Dictionary<string, string> CustomValues { get { return _customValues; } }
}
ConfigurationManager.AppSettings
是一个NameValueCollection
,它不是线程安全的,因此如果没有适当同步,使用它进行并行测试不是一个好主意。否则,您可以在TestInitialize
/构造函数中调用ConfigurationManager.AppSettings.Clear()
,就可以避免这个问题了。 - Ohad Schneider