C# 如何使用moq模拟ConfigurationManager.AppSettings

C# 如何使用moq模拟ConfigurationManager.AppSettings,c#,unit-testing,moq,C#,Unit Testing,Moq,我被困在我不知道如何模仿的代码的这一点上: ConfigurationManager.AppSettings["User"]; 我不得不嘲笑ConfigurationManager,但我一点也不知道,我正在使用它 有人能给我小费吗?谢谢 我认为一种标准方法是使用facade模式来包装configuration manager,然后您可以控制松散耦合的东西 因此,您需要包装ConfigurationManager。比如: public class Configuration: IConfigur

我被困在我不知道如何模仿的代码的这一点上:

ConfigurationManager.AppSettings["User"];
我不得不嘲笑ConfigurationManager,但我一点也不知道,我正在使用它


有人能给我小费吗?谢谢

我认为一种标准方法是使用facade模式来包装configuration manager,然后您可以控制松散耦合的东西

因此,您需要包装ConfigurationManager。比如:

public class Configuration: IConfiguration
{
    public User
    {
        get
        { 
            return ConfigurationManager.AppSettings["User"];
        }
    }
}
(您可以从配置类中提取接口,然后在代码中的任何地方使用该接口)
然后,您只需模拟IConfiguration。您可以通过几种不同的方式实现外观本身。在上面,我选择只包装单个属性。您还可以获得使用强类型信息而不是弱类型散列数组的附带好处。

可能不是您需要完成的,但是您是否考虑过在测试项目中使用app.config? 因此ConfigurationManager将获得您在app.config中输入的值,您无需模拟任何内容。
这个解决方案非常适合我的需要,因为我从来不需要测试“变量”配置文件。

这是一个静态属性,Moq设计用于Moq实例方法或类,可以通过继承进行模拟。换句话说,Moq在这里对你没有任何帮助

对于模拟静态,我使用了一个名为的工具,它是免费的。还有其他框架隔离工具,比如Typemock也可以做到这一点,尽管我相信这些是付费工具

当涉及到静态和测试时,另一种选择是自己创建静态状态,尽管这通常会有问题(我想在您的情况下会是这样)


最后,如果隔离框架不是一种选择,并且您致力于这种方法,Joshua提到的facade是一种很好的方法,或者任何通常的方法,其中您将客户机代码从用于测试的业务逻辑中分离出来。

您可以使用垫片将
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.        
    }
}

有关垫片和赝品的更多信息,请访问。希望这有帮助

我正在使用AspnetMvc4。刚才我写了

ConfigurationManager.AppSettings["mykey"] = "myvalue";
在我的测试方法中,它非常有效

说明:测试方法在应用程序设置取自的上下文中运行,通常是
web.config
myapp.config
ConfigurationsManager
可以访问此应用程序全局对象并对其进行操作


不过:如果您有一个并行运行测试的测试运行程序,这不是一个好主意。

您是否考虑过使用存根而不是模拟?
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。

我认为编写自己的app.config提供程序是一项简单的任务,比任何其他任务都有用。特别是你应该避免任何假货,如垫片等,因为一旦你使用它们,“编辑并继续”就不再有效

我使用的提供者如下所示:

默认情况下,它们从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; } }
}
公共类AppConfigProvider
{
公共AppConfigProvider()
{
ConnectionStrings=新的ConnectionStringsProvider();
AppSettings=新建AppSettingsProvider();
}
公共连接字符串提供程序连接字符串{get;private set;}
公共应用程序设置提供程序应用程序设置{get;private set;}
}
公共类连接字符串提供程序
{
私有只读字典_customValues=新字典(StringComparer.OrdinalIgnoreCase);
公共字符串此[字符串键]
{
得到
{
字符串自定义值;
if(_customValues.TryGetValue(键,out customValue))
{
返回自定义值;
}
var connectionStringSettings=ConfigurationManager.ConnectionStrings[key];
返回connectionStringSettings==null?null:connectionStringSettings.ConnectionString;
}
}
公共字典CustomValues{get{return}CustomValues;}
}
公共类应用程序设置提供程序
{
私有只读字典_customValues=新字典(StringComparer.OrdinalIgnoreCase);
公共字符串此[字符串键]
{
得到
{
字符串自定义值;
返回_customValues.TryGetValue(键,out customValue)?customValue:ConfigurationManager.AppSettings[键];
}
}
公共字典CustomValues{get{return}CustomValues;}
}

设置你需要的东西怎么样?因为,我不想嘲笑.NET,是吗

System.Configuration.ConfigurationManager.AppSettings["myKey"] = "myVal";

您可能应该事先清理应用程序设置,以确保应用程序只看到您希望它看到的内容。

实现此目标的另一种方法是只提供您自己的
IConfiguration
,从您希望它从中提取的任何文件中提取,如下所示:

var builder = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
         .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();

现在,只要您在这个JSON文件中有测试所需的值,就可以很容易地重写和更改值。

从概念上讲,这也是我正在做的。但是,我使用Castle DictionaryAdapter(核心的一部分),它动态生成接口的实现。我不久前就写过这篇文章:(向下滚动到“解决方案”,看看我是如何使用Castle DictionaryAdapter的)这篇文章很漂亮,也是一篇好文章。在将来,我必须记住这一点。我还可以添加—取决于您的纯粹主义和解释—这可以替代,也可以称为委托代理或适配器