Jak mockować ConfigurationManager.AppSettings za pomocą moq

124

Utknąłem w tym miejscu kodu, z którego nie wiem jak wyszydzić:

ConfigurationManager.AppSettings["User"];

Muszę kpić z ConfigurationManager, ale nie mam pojęcia, używam Moq .

Ktoś może dać mi wskazówkę? Dzięki!

Otuyh
źródło

Odpowiedzi:

105

Uważam, że jednym standardowym podejściem do tego jest użycie wzorca fasady do owinięcia menedżera konfiguracji, a wtedy masz coś luźno powiązanego, nad którym masz kontrolę.

Więc opakowałbyś plik ConfigurationManager. Coś jak:

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

(Możesz po prostu wyodrębnić interfejs z klasy konfiguracji, a następnie użyć tego interfejsu wszędzie w kodzie). Następnie po prostu mockujesz IConfiguration. Możesz być w stanie zaimplementować samą elewację na kilka różnych sposobów. Powyżej wybrałem po prostu zawinięcie poszczególnych właściwości. Dodatkową korzyścią jest również posiadanie silnie wpisanych informacji do pracy, a nie słabo wpisanych tablic mieszających.

Joshua Enfield
źródło
6
Robię to również pod względem koncepcyjnym. Jednak używam Castle DictionaryAdapter (część Castle Core), który generuje implementację interfejsu w locie. Pisałem o tym jakiś czas temu: blog.andreloker.de/post/2008/09/05/ ... (przewiń w dół do "Rozwiązania", aby zobaczyć, jak używam Castle DictionaryAdapter)
Andre Loker
To fajne i to jest dobry artykuł. Muszę o tym pamiętać na przyszłość.
Joshua Enfield
Mógłbym również dodać - w zależności od twojego puryzmu i interpretacji - zamiast tego można to nazwać delegatem proxy lub adapterem.
Joshua Enfield
3
Z góry po prostu używa Moq jako „normalnego”. Nietestowane, ale coś w stylu: var configurationMock = new Mock<IConfiguration>();i dla konfiguracji:configurationMock.SetupGet(s => s.User).Returns("This is what the user property returns!");
Joshua Enfield
Ten scenariusz jest używany, gdy warstwa jest zależna od IConfiguration i musisz mockować IConfiguration, ale jak przetestujesz implementację IConfiguration? A jeśli wywołasz test jednostkowy ConfigurationManager.AppSettings ["User"], to nie przetestuje jednostki, ale przetestuje, jakie wartości są pobierane z pliku konfiguracyjnego, który nie jest testem jednostkowym. Jeśli chcesz sprawdzić implementację, zobacz @ zpbappi.com/testing-codes-with-configurationmanager-appsettings
nkalfov
174

Używam AspnetMvc4. Przed chwilą pisałem

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

w mojej metodzie testowej i zadziałało idealnie.

Objaśnienie: metoda testowa działa w kontekście z ustawieniami aplikacji pobranymi z, zwykle a web.configlub myapp.config. ConfigurationsManagermoże dotrzeć do tego globalnego obiektu aplikacji i nim manipulować.

Chociaż: jeśli masz biegacza testowego, który równolegle przeprowadza testy, nie jest to dobry pomysł.

LosManos
źródło
8
To naprawdę sprytny i prosty sposób na rozwiązanie problemu! Uznanie za prostotę!
Navap
1
W większości przypadków dużo łatwiejsze niż tworzenie abstrakcji
Michael Clark
2
Otóż ​​to???? Błyskotliwość tkwi w prostocie, kiedy zastanawiałem się, jak przetestować tę konkretną zapieczętowaną klasę.
Piotr Kula
6
ConfigurationManager.AppSettings jest NameValueCollection który nie jest bezpieczny dla wątków, więc równoległe testy z jego użyciem bez odpowiedniej synchronizacji i tak nie są dobrym pomysłem. W przeciwnym razie możesz po prostu sprawdzić ConfigurationManager.AppSettings.Clear()swojego TestInitialize/ ctora i jesteś złoty.
Ohad Schneider
1
Proste i zwięzłe. Zdecydowanie najlepsza odpowiedź!
znn
21

Może nie jest to to, co musisz osiągnąć, ale czy rozważałeś użycie pliku app.config w swoim projekcie testowym? Więc ConfigurationManager otrzyma wartości, które umieścisz w app.config i nie musisz niczego mockować. To rozwiązanie działa dobrze na moje potrzeby, ponieważ nigdy nie muszę testować pliku konfiguracyjnego „zmiennej”.

Iridio
źródło
7
Jeśli zachowanie testowanego kodu zmienia się w zależności od wartości wartości konfiguracyjnej, z pewnością łatwiej jest go przetestować, jeśli nie zależy bezpośrednio od AppSettings.
Andre Loker
2
To zła praktyka, ponieważ nigdy nie testujesz innych możliwych ustawień. Odpowiedź Joshua Enfielda jest świetna do testów.
mkaj
4
Podczas gdy inni są przeciwni tej odpowiedzi, powiedziałbym, że ich stanowisko jest nieco uogólnione. To bardzo ważna odpowiedź w niektórych scenariuszach i tak naprawdę zależy tylko od Twoich potrzeb. Na przykład, powiedzmy, że mam 4 różne klastry, z których każdy ma inny podstawowy adres URL. Te 4 klastry są pobierane w czasie wykonywania z projektu Web.configobejmującego. Podczas testowania pobieranie niektórych dobrze znanych wartości z programu app.configjest bardzo ważne. Test jednostkowy musi tylko upewnić się, że warunki w momencie ściągania działają, na przykład „klaster1”; w tym przypadku istnieją tylko 4 różne klastry.
Mike Perrenoud
14

Możesz użyć podkładek, aby zmodyfikować obiekt AppSettingsniestandardowy NameValueCollection. Oto przykład, jak możesz to osiągnąć:

[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.        
    }
}

Więcej informacji na temat podkładek i podróbek można znaleźć pod adresem Isolating Code Under Test with Microsoft Fakes . Mam nadzieję że to pomoże.

Zorayr
źródło
6
Autor prosi o poradę z moq, a nie o podróbki MS.
JPCF
6
A jak to się różni? Osiąga mockowanie poprzez usuwanie zależności danych z jego kodu. Używanie C # Fakes to jedno podejście!
Zorayr
9

Czy zastanawiałeś się nad kiciem zamiast kpiny? PlikAppSettingsObiekt jest 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...
    }
}

Korzyści to prostsza implementacja i brak zależności od System.Configuration, dopóki naprawdę tego nie potrzebujesz.

DanielLarsenNZ
źródło
3
Najbardziej podoba mi się to podejście. Z jednej strony, zawijanie menedżera konfiguracji za pomocą, IConfigurationjak sugeruje Joshua Enfield, może być zbyt wysokim poziomem i możesz przegapić istniejące błędy z powodu takich rzeczy, jak złe analizowanie wartości konfiguracji. Z drugiej strony, używanie ConfigurationManager.AppSettingsbezpośrednio, jak sugeruje LosManos, jest zbyt dużym szczegółem implementacji, nie wspominając o tym, że może mieć skutki uboczne w innych testach i nie może być używane w równoległych przebiegach testów bez ręcznej synchronizacji (ponieważ NameValueConnectionnie jest bezpieczne dla wątków).
Ohad Schneider
2

To jest właściwość statyczna, a Moq jest przeznaczony do metod lub klas instancji Moq, które mogą być mockowane przez dziedziczenie. Innymi słowy, Moq nie pomoże ci tutaj.

Do kpiny ze statyki używam narzędzia o nazwie Moles , które jest bezpłatne. Istnieją inne narzędzia do izolacji struktury, takie jak Typemock, które również mogą to zrobić, chociaż uważam, że są to narzędzia płatne.

Jeśli chodzi o statykę i testowanie, inną opcją jest samodzielne utworzenie stanu statycznego, chociaż często może to być problematyczne (tak jak wyobrażam sobie, że byłoby to w twoim przypadku).

I wreszcie, jeśli ramy izolacji nie wchodzą w grę i jesteś oddany temu podejściu, fasada, o której wspomniał Joshua, jest dobrym podejściem lub ogólnie dowolnym podejściem, w którym kod klienta należy oddzielić od logiki biznesowej, którą używam do testowania.

Erik Dietrich
źródło
1

Myślę, że napisanie własnego dostawcy app.config jest prostym zadaniem i jest bardziej przydatne niż cokolwiek innego. Szczególnie należy unikać wszelkich podróbek, takich jak podkładki itp., Ponieważ gdy tylko ich użyjesz, Edycja i Kontynuuj przestaje działać.

Dostawcy, których używam, wyglądają tak:

Domyślnie pobierają wartości z, App.configale w przypadku testów jednostkowych mogę zastąpić wszystkie wartości i używać ich w każdym teście niezależnie.

Nie ma potrzeby stosowania żadnych interfejsów ani implementowania go za każdym razem. Mam bibliotekę DLL z narzędziami i używam tego małego pomocnika w wielu projektach i testach jednostkowych.

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
źródło
1

Co powiesz na ustawienie tego, czego potrzebujesz? Ponieważ nie chcę kpić z .NET, prawda?

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

Prawdopodobnie powinieneś wcześniej wyczyścić AppSettings, aby upewnić się, że aplikacja widzi tylko to, co chcesz.

Eike
źródło