Как издеваться над ConfigurationManager.AppSettings с помощью moq

124

Я застрял на этом участке кода, над которым не знаю, как издеваться:

ConfigurationManager.AppSettings["User"];

Мне нужно издеваться над ConfigurationManager, но я понятия не имею, я использую Moq .

Кто-нибудь может дать мне совет? Спасибо!

Otuyh
источник

Ответы:

105

Я считаю, что один из стандартных подходов к этому - использовать шаблон фасада для обертывания диспетчера конфигурации, и тогда у вас будет что-то слабо связанное, над которым вы можете контролировать.

Таким образом, вы должны обернуть ConfigurationManager. Что-то вроде:

public class Configuration: IConfiguration
{
    public User
    {
        get
        { 
            return ConfigurationManager.AppSettings["User"];
        }
    }
}

(Вы можете просто извлечь интерфейс из своего класса конфигурации, а затем использовать этот интерфейс повсюду в своем коде). Затем вы просто имитируете IConfiguration. Вы могли бы реализовать сам фасад несколькими разными способами. Выше я решил просто обернуть отдельные свойства. Вы также получаете дополнительное преимущество в виде строго типизированной информации для работы с массивами хешей со слабой типизацией.

Джошуа Энфилд
источник
6
Я тоже концептуально этим занимаюсь. Однако я использую Castle DictionaryAdapter (часть Castle Core), который генерирует реализацию интерфейса на лету. Я писал об этом некоторое время назад: blog.andreloker.de/post/2008/09/05/… (прокрутите вниз до «Решение», чтобы увидеть, как я использую Castle DictionaryAdapter)
Андре Локер
Это отличная и хорошая статья. Придется иметь это в виду на будущее.
Джошуа Энфилд
Я мог бы также добавить - в зависимости от вашего пуризма и интерпретации - это могло бы вместо этого или также называться прокси-сервером делегата или адаптером.
Джошуа Энфилд
3
Сверху он просто использует Moq как «нормальный». Непроверено, но что-то вроде: var configurationMock = new Mock<IConfiguration>();и для настройки:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Джошуа Энфилд
Этот сценарий используется, когда уровень зависит от IConfiguration и вам нужно имитировать IConfiguration, но как бы вы протестировали реализацию IConfiguration? И если вы вызываете Unit Test ConfigurationManager.AppSettings ["User"], то это не будет проверять модуль, но проверяет, какие значения выбираются из файла конфигурации, что не является модульным тестом. Если вам нужно проверить реализацию, см. @ Zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov
174

Я использую AspnetMvc4. Мгновение назад я написал

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

в моем методе тестирования, и он работал отлично.

Объяснение: метод тестирования запускается в контексте с настройками приложения, обычно из web.configили myapp.config. ConfigurationsManagerможет достичь этого глобального объекта приложения и манипулировать им.

Хотя: если у вас есть тестер, запускающий тесты параллельно, это не лучшая идея.

LosManos
источник
8
Это действительно умный и простой способ решить проблему! Престижность за простоту!
Navap
1
В большинстве случаев гораздо проще, чем создавать абстракцию,
Майкл Кларк,
2
Это оно???? Великолепие заключается в простоте, когда я ломал голову над тем, как протестировать именно этот закрытый класс.
Петр Кула
6
ConfigurationManager.AppSettingsне NameValueCollectionявляется потокобезопасным, поэтому параллельные тесты с его использованием без надлежащей синхронизации в любом случае не являются хорошей идеей. В противном случае вы можете просто позвонить ConfigurationManager.AppSettings.Clear()в свой TestInitialize/ ctor, и вы золотой.
Охад Шнайдер
1
Просто и лаконично. Безусловно, лучший ответ!
znn 07
21

Возможно, это не то, что вам нужно сделать, но рассматривали ли вы возможность использовать app.config в своем тестовом проекте? Таким образом, ConfigurationManager получит значения, которые вы указали в app.config, и вам не нужно ничего имитировать. Это решение хорошо подходит для моих нужд, потому что мне никогда не нужно тестировать "переменный" файл конфигурации.

Iridio
источник
7
Если поведение тестируемого кода изменяется в зависимости от значения значения конфигурации, его, безусловно, легче проверить, если оно не зависит напрямую от AppSettings.
Андре Локер
2
Это плохая практика, поскольку вы никогда не проверяете другие возможные настройки. Ответ Джошуа Энфилда отлично подходит для тестирования.
mkaj
4
Хотя другие против этого ответа, я бы сказал, что их позиция несколько обобщена. Это очень верный ответ в некоторых сценариях, и на самом деле он просто зависит от того, что вам нужно. Например, предположим, что у меня есть 4 разных кластера, каждый из которых имеет свой базовый URL. Эти 4 кластера извлекаются во время выполнения из Web.configокружающего проекта. Во время тестирования app.configвполне допустимо извлечение некоторых хорошо известных значений из . Модульному тесту просто необходимо убедиться, что условия при его загрузке, скажем, "cluster1" работают; в этом случае есть только 4 разных кластера.
Майк Перрено
14

Вы можете использовать прокладки для изменения 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» . Надеюсь это поможет.

Zorayr
источник
6
Автор спрашивает, как работать с moq, а не о MS Fakes.
JPCF
6
А чем это отличается? Он достигает насмешки, удаляя зависимость данных из своего кода. Использование C # Fakes - один из подходов!
Зорайр
9

Вы не думали о том, чтобы издеваться над заглушкой? 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 до тех пор, пока он вам действительно не понадобится.

DanielLarsenNZ
источник
3
Мне больше всего нравится такой подход. С одной стороны, упаковка диспетчера конфигурации с помощью, IConfigurationкак предлагает Джошуа Энфилд, может быть слишком высоким уровнем, и вы можете пропустить ошибки, которые существуют из-за таких вещей, как неправильный синтаксический анализ значения конфигурации. С другой стороны, использование ConfigurationManager.AppSettingsнапрямую, как предлагает LosManos, - это слишком большая часть деталей реализации, не говоря уже о том, что оно может иметь побочные эффекты для других тестов и не может использоваться в параллельных тестовых прогонах без ручной синхронизации (поскольку NameValueConnectionне является потокобезопасным).
Охад Шнайдер
2

Это статическое свойство, и Moq предназначен для методов или классов экземпляров Moq, которые можно имитировать с помощью наследования. Другими словами, Moq здесь не поможет.

Для имитации статики я использую бесплатный инструмент Moles . Есть и другие инструменты изоляции фреймворка, такие как Typemock, которые тоже могут это сделать, хотя я считаю, что это платные инструменты.

Когда дело доходит до статики и тестирования, другой вариант - создать статическое состояние самостоятельно, хотя это часто может быть проблематичным (как, я полагаю, это было бы в вашем случае).

И, наконец, если фреймворки изоляции не подходят, и вы привержены этому подходу, фасад, упомянутый Джошуа, является хорошим подходом или любым подходом в целом, когда вы учитываете клиентский код этого отдельно от бизнес-логики, которую вы Использую для проверки.

Эрик Дитрих
источник
1

Я думаю, что написать собственный провайдер app.config - это простая задача, и она более полезна, чем что-либо еще. Особенно вам следует избегать любых подделок, таких как прокладки и т. Д., Потому что, как только вы их используете, Edit & Continue больше не работает.

Провайдеры, которых я использую, выглядят так:

По умолчанию они получают значения из, 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; } }
}
t3chb0t
источник
1

Как насчет того, чтобы просто установить то, что вам нужно? Потому что я не хочу издеваться над .NET, не так ли ...?

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

Вероятно, вам следует заранее очистить AppSettings, чтобы убедиться, что приложение видит только то, что вы хотите.

Эйке
источник