如何使用 moq 模拟 ConfigurationManager.AppSettings

Posted

技术标签:

【中文标题】如何使用 moq 模拟 ConfigurationManager.AppSettings【英文标题】:How to mock ConfigurationManager.AppSettings with moq 【发布时间】:2012-03-18 04:04:12 【问题描述】:

我被困在我不知道如何模拟的代码点:

ConfigurationManager.AppSettings["User"];

我必须模拟 ConfigurationManager,但我不知道,我正在使用 Moq。

有人可以给我小费吗?谢谢!

【问题讨论】:

【参考方案1】:

我担心我需要回忆我说过的话。 ConfigurationManager.AppSettings 偶尔表现得很奇怪,就像它不会总是立即产生刚刚写入的值。因此,我们的构建机器上出现了零星的单元测试失败。我不得不重写我的代码以使用包装器,在通常情况下返回 ConfigurationManager.AppSettings,在单元测试中返回测试值。

设置你需要的怎么样?因为,我不想模拟 .NET,难道我...?

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

您可能应该事先清除 AppSettings 以确保应用只看到您想要的内容。

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

我相信解决这个问题的一种标准方法是使用 facade 模式来包装配置管理器,然后您就可以控制一些松散耦合的东西。

因此您将包装 ConfigurationManager。比如:

public class Configuration: IConfiguration

    public User
    
        get
         
            return ConfigurationManager.AppSettings["User"];
        
    

(您可以从配置类中提取一个接口,然后在代码中的任何地方使用该接口) 然后你只需模拟 IConfiguration。您可能能够以几种不同的方式实现外观本身。上面我选择只包装各个属性。您还可以获得使用强类型信息而不是弱类型哈希数组的附带好处。

【讨论】:

这也是我正在做的概念。但是,我使用 Castle DictionaryAdapter(Castle Core 的一部分)动态生成接口的实现。我前段时间写过:blog.andreloker.de/post/2008/09/05/…(向下滚动到“解决方案”以查看我如何使用 Castle DictionaryAdapter) 这很漂亮,而且是一篇好文章。我必须牢记这一点,以备不时之需。 我还可以添加 - 根据您的纯粹性和解释 - 这可以替代或也称为委托代理或适配器。 从上面它只是使用最小起订量作为“正常”。未经测试,但类似于:var configurationMock = new Mock<IConfiguration>(); 和设置:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!"); 当层依赖于 IConfiguration 并且您需要模拟 IConfiguration 时使用此场景,但是您将如何测试 IConfiguration 实现?如果您调用单元测试 ConfigurationManager.AppSettings["User"] 那么这不会测试单元,但会测试从配置文件中获取的值,这不是单元测试。如果需要检查实现,见@zpbappi.com/testing-codes-with-configurationmanager-appsettings【参考方案4】:

我认为编写自己的 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;  

【讨论】:

【参考方案5】:

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

【讨论】:

我最喜欢这种方法。一方面,像 Joshua Enfield 所建议的那样用IConfiguration 包装配置管理器可能级别太高,并且您可能会错过由于错误的配置值解析等原因而存在的错误。另一方面,像 LosManos 建议的那样直接使用 ConfigurationManager.AppSettings 是一个太多的实现细节,更不用说它会对其他测试产生副作用,并且不能在没有手动同步的情况下用于并行测试运行(因为 NameValueConnection 不是线程安全)。【参考方案6】:

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

ConfigurationManager.AppSettings["mykey"] = "myvalue";

在我的测试方法中,它运行良好。

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

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

【讨论】:

这真是个聪明又简单的解决问题的方法!感谢简单! 在大多数情况下比创建抽象要容易得多 就这样????当我绞尽脑汁思考如何测试这个特殊的密封类时,我的聪明之处就在于简单。 ConfigurationManager.AppSettings 是一个NameValueCollection,它不是线程安全的,因此在没有适当同步的情况下使用它进行并行测试无论如何都不是一个好主意。否则,您只需在您的 TestInitialize / ctor 中调用 ConfigurationManager.AppSettings.Clear() 就可以了。 简洁明了。到目前为止最好的答案!【参考方案7】:

您可以使用 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.        
    

您可以在Isolating Code Under Test with Microsoft Fakes 上阅读有关垫片和假货的更多信息。希望这可以帮助。

【讨论】:

作者询问的是如何使用最小起订量,而不是 MS Fakes。 这有什么不同?它通过从他的代码中删除数据依赖来实现模拟。使用 C# Fakes 是一种方法!【参考方案8】:

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

【讨论】:

如果被测代码的行为根据配置值的值而改变,如果它不直接依赖于 AppSettings,测试它肯定更容易。 这是不好的做法,因为您从不测试其他可能的设置。 Joshua Enfield 的回答非常适合测试。 虽然其他人反对这个答案,但我想说他们的立场有点笼统。在某些情况下,这是一个非常有效的答案,它实际上仅取决于您的需求。例如,假设我有 4 个不同的集群,每个集群都有不同的基本 URL。这 4 个集群在运行时从包含该项目的 Web.config 中提取。在测试期间,从app.config 中提取一些众所周知的值是非常有效的。单元测试只需要确保拉出“cluster1”时的条件有效;在这种情况下,只有 4 个不同的集群。【参考方案9】:

那是一个静态属性,Moq 被设计为 Moq 实例方法或类,可以通过继承来模拟。换句话说,起订量在这里对您没有任何帮助。

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

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

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

【讨论】:

以上是关于如何使用 moq 模拟 ConfigurationManager.AppSettings的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Moq 框架模拟 ModelState.IsValid?

使用 Moq 模拟类时,如何仅针对特定方法进行 CallBase?

如何使用 Moq 在 ASP.NET MVC 中模拟 HttpContext?

如何使用 Moq 在 .NET Core 2.1 中模拟新的 HttpClientFactory

如何使用 Moq 从 IHttpClientFactory 模拟 HTTPClient 并结合 .NET Core 中的 Polly 策略

使用Moq模拟实体框架6 ObjectResult